From 30423b6a4d48ec9a8f24cdcd61bf39fa24205863 Mon Sep 17 00:00:00 2001 From: Jose Alberto Hernandez Date: Sun, 30 Jul 2023 22:25:16 -0600 Subject: [PATCH] FINERACT-1960: GL account mappings to use Savings product with Accrual accounting --- .../common/AccountingConstants.java | 266 ++++++++--------- .../common/AccountingValidations.java | 45 +++ .../savings/DepositsApiConstants.java | 12 +- .../SavingsAccountTransactionType.java | 39 ++- .../SavingsAccountTransactionEnumData.java | 2 + ...AccountMappingFromApiJsonDeserializer.java | 36 ++- ...AccountMappingReadPlatformServiceImpl.java | 206 ++++++++----- ...ccountMappingWritePlatformServiceImpl.java | 125 ++++---- ...avingsProductToGLAccountMappingHelper.java | 75 +++++ .../AccountingProcessorForSavingsFactory.java | 3 + .../service/AccountingProcessorHelper.java | 107 +++++++ ...ualBasedAccountingProcessorForSavings.java | 272 ++++++++++++++++++ .../LoanProductDataValidator.java | 24 +- ...ixedDepositProductsApiResourceSwagger.java | 113 ++------ ...ringDepositProductsApiResourceSwagger.java | 98 ++----- .../SavingsProductsApiResourceSwagger.java | 130 ++------- .../data/DepositProductDataValidator.java | 143 ++------- ...avingsAccountTransactionDataValidator.java | 13 +- ...SavingsProductAccountingDataValidator.java | 184 ++++++++++++ .../data/SavingsProductDataValidator.java | 193 ++----------- ...WritePlatformServiceJpaRepositoryImpl.java | 25 +- ...WritePlatformServiceJpaRepositoryImpl.java | 24 +- .../AccountingScenarioIntegrationTest.java | 118 +++++++- .../RecurringDepositProductHelper.java | 11 + .../common/savings/SavingsProductHelper.java | 54 ++++ 25 files changed, 1384 insertions(+), 934 deletions(-) create mode 100644 fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingValidations.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductAccountingDataValidator.java diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java index 257dc1f945b..9839f0d7004 100644 --- a/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java @@ -36,24 +36,11 @@ private AccountingConstants() { ***/ public enum CashAccountsForLoan { - FUND_SOURCE(1), // - LOAN_PORTFOLIO(2), // - INTEREST_ON_LOANS(3), // - INCOME_FROM_FEES(4), // - INCOME_FROM_PENALTIES(5), // - LOSSES_WRITTEN_OFF(6), // - TRANSFERS_SUSPENSE(10), // - OVERPAYMENT(11), // - INCOME_FROM_RECOVERY(12), // - GOODWILL_CREDIT(13), // - INCOME_FROM_CHARGE_OFF_INTEREST(14), // - INCOME_FROM_CHARGE_OFF_FEES(15), // - CHARGE_OFF_EXPENSE(16), // - CHARGE_OFF_FRAUD_EXPENSE(17), // - INCOME_FROM_CHARGE_OFF_PENALTY(18), // - INCOME_FROM_GOODWILL_CREDIT_INTEREST(19), // - INCOME_FROM_GOODWILL_CREDIT_FEES(20), // - INCOME_FROM_GOODWILL_CREDIT_PENALTY(21); // + FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF( + 6), TRANSFERS_SUSPENSE(10), OVERPAYMENT(11), INCOME_FROM_RECOVERY(12), GOODWILL_CREDIT(13), INCOME_FROM_CHARGE_OFF_INTEREST( + 14), INCOME_FROM_CHARGE_OFF_FEES(15), CHARGE_OFF_EXPENSE(16), CHARGE_OFF_FRAUD_EXPENSE( + 17), INCOME_FROM_CHARGE_OFF_PENALTY(18), INCOME_FROM_GOODWILL_CREDIT_INTEREST( + 19), INCOME_FROM_GOODWILL_CREDIT_FEES(20), INCOME_FROM_GOODWILL_CREDIT_PENALTY(21); private final Integer value; @@ -89,27 +76,12 @@ public static CashAccountsForLoan fromInt(final int i) { ***/ public enum AccrualAccountsForLoan { - FUND_SOURCE(1), // - LOAN_PORTFOLIO(2), // - INTEREST_ON_LOANS(3), // - INCOME_FROM_FEES(4), // - INCOME_FROM_PENALTIES(5), // - LOSSES_WRITTEN_OFF(6), // - INTEREST_RECEIVABLE(7), // - FEES_RECEIVABLE(8), // - PENALTIES_RECEIVABLE(9), // - TRANSFERS_SUSPENSE(10), // - OVERPAYMENT(11), // - INCOME_FROM_RECOVERY(12), // - GOODWILL_CREDIT(13), // - INCOME_FROM_CHARGE_OFF_INTEREST(14), // - INCOME_FROM_CHARGE_OFF_FEES(15), // - CHARGE_OFF_EXPENSE(16), // - CHARGE_OFF_FRAUD_EXPENSE(17), // - INCOME_FROM_CHARGE_OFF_PENALTY(18), // - INCOME_FROM_GOODWILL_CREDIT_INTEREST(19), // - INCOME_FROM_GOODWILL_CREDIT_FEES(20), // - INCOME_FROM_GOODWILL_CREDIT_PENALTY(21); // + FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), // + LOSSES_WRITTEN_OFF(6), INTEREST_RECEIVABLE(7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9), // + TRANSFERS_SUSPENSE(10), OVERPAYMENT(11), INCOME_FROM_RECOVERY(12), GOODWILL_CREDIT(13), INCOME_FROM_CHARGE_OFF_INTEREST( + 14), INCOME_FROM_CHARGE_OFF_FEES(15), CHARGE_OFF_EXPENSE(16), CHARGE_OFF_FRAUD_EXPENSE(17), INCOME_FROM_CHARGE_OFF_PENALTY( + 18), INCOME_FROM_GOODWILL_CREDIT_INTEREST( + 19), INCOME_FROM_GOODWILL_CREDIT_FEES(20), INCOME_FROM_GOODWILL_CREDIT_PENALTY(21); private final Integer value; @@ -146,33 +118,28 @@ public static AccrualAccountsForLoan fromInt(final int i) { ***/ public enum LoanProductAccountingParams { - FUND_SOURCE("fundSourceAccountId"), // - LOAN_PORTFOLIO("loanPortfolioAccountId"), // - INTEREST_ON_LOANS("interestOnLoanAccountId"), // - INCOME_FROM_FEES("incomeFromFeeAccountId"), // - INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), // - LOSSES_WRITTEN_OFF("writeOffAccountId"), // - GOODWILL_CREDIT("goodwillCreditAccountId"), // - OVERPAYMENT("overpaymentLiabilityAccountId"), // - INTEREST_RECEIVABLE("receivableInterestAccountId"), // - FEES_RECEIVABLE("receivableFeeAccountId"), // - PENALTIES_RECEIVABLE("receivablePenaltyAccountId"), // - TRANSFERS_SUSPENSE("transfersInSuspenseAccountId"), // - PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), // - PAYMENT_TYPE("paymentTypeId"), // - FEE_INCOME_ACCOUNT_MAPPING("feeToIncomeAccountMappings"), // - PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), // - CHARGE_ID("chargeId"), // - INCOME_ACCOUNT_ID("incomeAccountId"), // - INCOME_FROM_RECOVERY("incomeFromRecoveryAccountId"), // - INCOME_FROM_CHARGE_OFF_INTEREST("incomeFromChargeOffInterestAccountId"), // - INCOME_FROM_CHARGE_OFF_FEES("incomeFromChargeOffFeesAccountId"), // - CHARGE_OFF_EXPENSE("chargeOffExpenseAccountId"), // - CHARGE_OFF_FRAUD_EXPENSE("chargeOffFraudExpenseAccountId"), // - INCOME_FROM_CHARGE_OFF_PENALTY("incomeFromChargeOffPenaltyAccountId"), // - INCOME_FROM_GOODWILL_CREDIT_INTEREST("incomeFromGoodwillCreditInterestAccountId"), // - INCOME_FROM_GOODWILL_CREDIT_FEES("incomeFromGoodwillCreditFeesAccountId"), // - INCOME_FROM_GOODWILL_CREDIT_PENALTY("incomeFromGoodwillCreditPenaltyAccountId"); // + FUND_SOURCE("fundSourceAccountId"), LOAN_PORTFOLIO("loanPortfolioAccountId"), INTEREST_ON_LOANS( + "interestOnLoanAccountId"), INCOME_FROM_FEES("incomeFromFeeAccountId"), INCOME_FROM_PENALTIES( + "incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), GOODWILL_CREDIT( + "goodwillCreditAccountId"), OVERPAYMENT("overpaymentLiabilityAccountId"), INTEREST_RECEIVABLE( + "receivableInterestAccountId"), FEES_RECEIVABLE("receivableFeeAccountId"), PENALTIES_RECEIVABLE( + "receivablePenaltyAccountId"), TRANSFERS_SUSPENSE( + "transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING( + "paymentChannelToFundSourceMappings"), PAYMENT_TYPE( + "paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING( + "feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING( + "penaltyToIncomeAccountMappings"), CHARGE_ID( + "chargeId"), INCOME_ACCOUNT_ID( + "incomeAccountId"), INCOME_FROM_RECOVERY( + "incomeFromRecoveryAccountId"), INCOME_FROM_CHARGE_OFF_INTEREST( + "incomeFromChargeOffInterestAccountId"), INCOME_FROM_CHARGE_OFF_FEES( + "incomeFromChargeOffFeesAccountId"), CHARGE_OFF_EXPENSE( + "chargeOffExpenseAccountId"), CHARGE_OFF_FRAUD_EXPENSE( + "chargeOffFraudExpenseAccountId"), INCOME_FROM_CHARGE_OFF_PENALTY( + "incomeFromChargeOffPenaltyAccountId"), INCOME_FROM_GOODWILL_CREDIT_INTEREST( + "incomeFromGoodwillCreditInterestAccountId"), INCOME_FROM_GOODWILL_CREDIT_FEES( + "incomeFromGoodwillCreditFeesAccountId"), INCOME_FROM_GOODWILL_CREDIT_PENALTY( + "incomeFromGoodwillCreditPenaltyAccountId"); private final String value; @@ -192,29 +159,24 @@ public String getValue() { public enum LoanProductAccountingDataParams { - FUND_SOURCE("fundSourceAccount"), // - LOAN_PORTFOLIO("loanPortfolioAccount"), // - INTEREST_ON_LOANS("interestOnLoanAccount"), // - INCOME_FROM_FEES("incomeFromFeeAccount"), // - INCOME_FROM_PENALTIES("incomeFromPenaltyAccount"), // - LOSSES_WRITTEN_OFF("writeOffAccount"), // - GOODWILL_CREDIT("goodwillCreditAccount"), // - OVERPAYMENT("overpaymentLiabilityAccount"), // - INTEREST_RECEIVABLE("receivableInterestAccount"), // - FEES_RECEIVABLE("receivableFeeAccount"), // - PENALTIES_RECEIVABLE("receivablePenaltyAccount"), // - TRANSFERS_SUSPENSE("transfersInSuspenseAccount"), // - INCOME_ACCOUNT_ID("incomeAccount"), // - INCOME_FROM_RECOVERY("incomeFromRecoveryAccount"), // - LIABILITY_TRANSFER_SUSPENSE("liabilityTransferInSuspenseAccount"), // - INCOME_FROM_CHARGE_OFF_INTEREST("incomeFromChargeOffInterestAccount"), // - INCOME_FROM_CHARGE_OFF_FEES("incomeFromChargeOffFeesAccount"), // - CHARGE_OFF_EXPENSE("chargeOffExpenseAccount"), // - CHARGE_OFF_FRAUD_EXPENSE("chargeOffFraudExpenseAccount"), // - INCOME_FROM_CHARGE_OFF_PENALTY("incomeFromChargeOffPenaltyAccount"), // - INCOME_FROM_GOODWILL_CREDIT_INTEREST("incomeFromGoodwillCreditInterestAccount"), // - INCOME_FROM_GOODWILL_CREDIT_FEES("incomeFromGoodwillCreditFeesAccount"), // - INCOME_FROM_GOODWILL_CREDIT_PENALTY("incomeFromGoodwillCreditPenaltyAccount"); // + FUND_SOURCE("fundSourceAccount"), LOAN_PORTFOLIO("loanPortfolioAccount"), INTEREST_ON_LOANS( + "interestOnLoanAccount"), INCOME_FROM_FEES("incomeFromFeeAccount"), INCOME_FROM_PENALTIES( + "incomeFromPenaltyAccount"), LOSSES_WRITTEN_OFF("writeOffAccount"), GOODWILL_CREDIT( + "goodwillCreditAccount"), OVERPAYMENT("overpaymentLiabilityAccount"), INTEREST_RECEIVABLE( + "receivableInterestAccount"), FEES_RECEIVABLE("receivableFeeAccount"), PENALTIES_RECEIVABLE( + "receivablePenaltyAccount"), TRANSFERS_SUSPENSE( + "transfersInSuspenseAccount"), INCOME_ACCOUNT_ID( + "incomeAccount"), INCOME_FROM_RECOVERY( + "incomeFromRecoveryAccount"), LIABILITY_TRANSFER_SUSPENSE( + "liabilityTransferInSuspenseAccount"), INCOME_FROM_CHARGE_OFF_INTEREST( + "incomeFromChargeOffInterestAccount"), INCOME_FROM_CHARGE_OFF_FEES( + "incomeFromChargeOffFeesAccount"), CHARGE_OFF_EXPENSE( + "chargeOffExpenseAccount"), CHARGE_OFF_FRAUD_EXPENSE( + "chargeOffFraudExpenseAccount"), INCOME_FROM_CHARGE_OFF_PENALTY( + "incomeFromChargeOffPenaltyAccount"), INCOME_FROM_GOODWILL_CREDIT_INTEREST( + "incomeFromGoodwillCreditInterestAccount"), INCOME_FROM_GOODWILL_CREDIT_FEES( + "incomeFromGoodwillCreditFeesAccount"), INCOME_FROM_GOODWILL_CREDIT_PENALTY( + "incomeFromGoodwillCreditPenaltyAccount"); private final String value; @@ -237,16 +199,8 @@ public String getValue() { ***/ public enum CashAccountsForSavings { - 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); // + 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); private final Integer value; @@ -277,28 +231,63 @@ public static CashAccountsForSavings fromInt(final int i) { } } + /*** + * 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 intToEnumMap = new HashMap<>(); + + static { + for (final AccrualAccountsForSavings type : AccrualAccountsForSavings.values()) { + intToEnumMap.put(type.value, type); + } + } + + public static AccrualAccountsForSavings fromInt(final int i) { + final AccrualAccountsForSavings type = intToEnumMap.get(Integer.valueOf(i)); + return type; + } + } + /*** * Enum of all accounting related input parameter names used while creating/updating a savings product ***/ public enum SavingProductAccountingParams { - SAVINGS_REFERENCE("savingsReferenceAccountId"), // - SAVINGS_CONTROL("savingsControlAccountId"), // - INCOME_FROM_FEES("incomeFromFeeAccountId"), // - INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), // - INTEREST_ON_SAVINGS("interestOnSavingsAccountId"), // - PAYMENT_CHANNEL_FUND_SOURCE_MAPPING("paymentChannelToFundSourceMappings"), // - PAYMENT_TYPE("paymentTypeId"), // - FUND_SOURCE("fundSourceAccountId"), // - TRANSFERS_SUSPENSE("transfersInSuspenseAccountId"), // - FEE_INCOME_ACCOUNT_MAPPING("feeToIncomeAccountMappings"), // - PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), // - CHARGE_ID("chargeId"), // - INCOME_ACCOUNT_ID("incomeAccountId"), // - OVERDRAFT_PORTFOLIO_CONTROL("overdraftPortfolioControlId"), // - INCOME_FROM_INTEREST("incomeFromInterestId"), // - LOSSES_WRITTEN_OFF("writeOffAccountId"), // - ESCHEAT_LIABILITY("escheatLiabilityId"); // + SAVINGS_REFERENCE("savingsReferenceAccountId"), SAVINGS_CONTROL("savingsControlAccountId"), INCOME_FROM_FEES( + "incomeFromFeeAccountId"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccountId"), INTEREST_ON_SAVINGS( + "interestOnSavingsAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING( + "paymentChannelToFundSourceMappings"), PAYMENT_TYPE("paymentTypeId"), FUND_SOURCE( + "fundSourceAccountId"), TRANSFERS_SUSPENSE( + "transfersInSuspenseAccountId"), FEE_INCOME_ACCOUNT_MAPPING( + "feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING( + "penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID( + "incomeAccountId"), OVERDRAFT_PORTFOLIO_CONTROL( + "overdraftPortfolioControlId"), INCOME_FROM_INTEREST( + "incomeFromInterestId"), LOSSES_WRITTEN_OFF( + "writeOffAccountId"), ESCHEAT_LIABILITY( + "escheatLiabilityId"), PENALTIES_RECEIVABLE("penaltiesReceivable"), + FEES_RECEIVABLE("feesReceivable"), INTEREST_PAYABLE("interestPayable"); private final String value; @@ -318,22 +307,17 @@ public String getValue() { public enum SavingProductAccountingDataParams { - SAVINGS_REFERENCE("savingsReferenceAccount"), // - SAVINGS_CONTROL("savingsControlAccount"), // - INCOME_FROM_FEES("incomeFromFeeAccount"), // - INCOME_FROM_PENALTIES("incomeFromPenaltyAccount"), // - INTEREST_ON_SAVINGS("interestOnSavingsAccount"), // - PAYMENT_TYPE("paymentType"), // - FUND_SOURCE("fundSourceAccount"), // - TRANSFERS_SUSPENSE("transfersInSuspenseAccount"), // - PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), // - CHARGE_ID("charge"), // - INCOME_ACCOUNT_ID("incomeAccount"), // - OVERDRAFT_PORTFOLIO_CONTROL("overdraftPortfolioControl"), // - INCOME_FROM_INTEREST("incomeFromInterest"), // - LOSSES_WRITTEN_OFF("writeOffAccount"), // - ESCHEAT_LIABILITY("escheatLiabilityAccount"); // - + SAVINGS_REFERENCE("savingsReferenceAccount"), SAVINGS_CONTROL("savingsControlAccount"), INCOME_FROM_FEES( + "incomeFromFeeAccount"), INCOME_FROM_PENALTIES("incomeFromPenaltyAccount"), INTEREST_ON_SAVINGS( + "interestOnSavingsAccount"), PAYMENT_TYPE("paymentType"), FUND_SOURCE("fundSourceAccount"), TRANSFERS_SUSPENSE( + "transfersInSuspenseAccount"), PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), CHARGE_ID( + "charge"), INCOME_ACCOUNT_ID("incomeAccount"), OVERDRAFT_PORTFOLIO_CONTROL( + "overdraftPortfolioControl"), INCOME_FROM_INTEREST( + "incomeFromInterest"), LOSSES_WRITTEN_OFF("writeOffAccount"), ESCHEAT_LIABILITY( + "escheatLiabilityAccount"), FEES_RECEIVABLE( + "feeReceivableAccount"), PENALTIES_RECEIVABLE( + "penaltyReceivableAccount"), INTEREST_PAYABLE( + "interestPayableAccount"); private final String value; SavingProductAccountingDataParams(final String value) { @@ -352,13 +336,11 @@ public String getValue() { public enum FinancialActivity { - ASSET_TRANSFER(100, "assetTransfer", GLAccountType.ASSET), // - LIABILITY_TRANSFER(200, "liabilityTransfer", GLAccountType.LIABILITY), // - CASH_AT_MAINVAULT(101, "cashAtMainVault", GLAccountType.ASSET), // - CASH_AT_TELLER(102, "cashAtTeller", GLAccountType.ASSET), // - OPENING_BALANCES_TRANSFER_CONTRA(300, "openingBalancesTransferContra", GLAccountType.EQUITY), // - ASSET_FUND_SOURCE(103, "fundSource", GLAccountType.ASSET), // - PAYABLE_DIVIDENDS(201, "payableDividends", GLAccountType.LIABILITY); // + ASSET_TRANSFER(100, "assetTransfer", GLAccountType.ASSET), LIABILITY_TRANSFER(200, "liabilityTransfer", + GLAccountType.LIABILITY), CASH_AT_MAINVAULT(101, "cashAtMainVault", GLAccountType.ASSET), CASH_AT_TELLER(102, + "cashAtTeller", GLAccountType.ASSET), OPENING_BALANCES_TRANSFER_CONTRA(300, "openingBalancesTransferContra", + GLAccountType.EQUITY), ASSET_FUND_SOURCE(103, "fundSource", + GLAccountType.ASSET), PAYABLE_DIVIDENDS(201, "payableDividends", GLAccountType.LIABILITY); private final Integer value; private final String code; @@ -470,10 +452,8 @@ public static CashAccountsForShares fromInt(final int i) { ***/ public enum SharesProductAccountingParams { - SHARES_REFERENCE("shareReferenceId"), // - SHARES_SUSPENSE("shareSuspenseId"), // - INCOME_FROM_FEES("incomeFromFeeAccountId"), // - SHARES_EQUITY("shareEquityId"); // + SHARES_REFERENCE("shareReferenceId"), SHARES_SUSPENSE("shareSuspenseId"), INCOME_FROM_FEES("incomeFromFeeAccountId"), SHARES_EQUITY( + "shareEquityId"); private final String value; diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingValidations.java b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingValidations.java new file mode 100644 index 00000000000..70c7613ae47 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingValidations.java @@ -0,0 +1,45 @@ +/** + * 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 class AccountingValidations { + + protected AccountingValidations() {} + + public static boolean isCashBasedAccounting(final Integer accountingRuleType) { + return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType); + } + + public static boolean isAccrualPeriodicBasedAccounting(final Integer accountingRuleType) { + return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingRuleType); + } + + public static boolean isUpfrontAccrualAccounting(final Integer accountingRuleType) { + return AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingRuleType); + } + + public static boolean isAccrualBasedAccounting(final Integer accountingRuleType) { + return AccountingRuleType.ACCRUAL_PERIODIC.getValue().equals(accountingRuleType) + || AccountingRuleType.ACCRUAL_UPFRONT.getValue().equals(accountingRuleType); + } + + public static boolean isCashOrAccrualBasedAccounting(final Integer accountingRuleType) { + return isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType); + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java index 517003b7f3e..c0162eb1aac 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java @@ -214,12 +214,14 @@ private DepositsApiConstants() { interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, accountingRuleParamName, chargesParamName, SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), - SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), + SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), SavingProductAccountingParams.FEES_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(), chartsParamName, - SavingsApiConstants.withHoldTaxParamName, SavingsApiConstants.taxGroupIdParamName)); + SavingProductAccountingParams.PENALTIES_RECEIVABLE.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.INTEREST_PAYABLE.getValue(), chartsParamName, SavingsApiConstants.withHoldTaxParamName, + SavingsApiConstants.taxGroupIdParamName)); private static final Set PRECLOSURE_REQUEST_DATA_PARAMETERS = new HashSet<>( Arrays.asList(preClosurePenalApplicableParamName, preClosurePenalInterestParamName, preClosurePenalInterestOnTypeIdParamName)); diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java index c9aa0e2302f..0621bd6273b 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java @@ -38,8 +38,9 @@ public enum SavingsAccountTransactionType { WITHDRAWAL_FEE(4, "savingsAccountTransactionType.withdrawalFee", TransactionEntryType.DEBIT), // ANNUAL_FEE(5, "savingsAccountTransactionType.annualFee", TransactionEntryType.DEBIT), // WAIVE_CHARGES(6, "savingsAccountTransactionType.waiveCharge"), // - PAY_CHARGE(7, "savingsAccountTransactionType.payCharge", TransactionEntryType.DEBIT), // - DIVIDEND_PAYOUT(8, "savingsAccountTransactionType.dividendPayout", TransactionEntryType.CREDIT), // + PAY_CHARGE(7, "savingsAccountTransactionType.payCharge"), // + DIVIDEND_PAYOUT(8, "savingsAccountTransactionType.dividendPayout"), // + ACCRUAL(10, "savingsAccountTransactionType.accrual"), // INITIATE_TRANSFER(12, "savingsAccountTransactionType.initiateTransfer"), // APPROVE_TRANSFER(13, "savingsAccountTransactionType.approveTransfer"), // WITHDRAW_TRANSFER(14, "savingsAccountTransactionType.withdrawTransfer"), // @@ -94,9 +95,33 @@ public boolean isDebitEntryType() { return entryType != null && entryType.isDebit(); } - public static SavingsAccountTransactionType fromInt(final Integer value) { - SavingsAccountTransactionType transactionType = BY_ID.get(value); - return transactionType == null ? INVALID : transactionType; + public static SavingsAccountTransactionType fromInt(final Integer transactionType) { + + if (transactionType == null) { + return SavingsAccountTransactionType.INVALID; + } + return switch (transactionType) { + case 1 -> SavingsAccountTransactionType.DEPOSIT; + case 2 -> SavingsAccountTransactionType.WITHDRAWAL; + case 3 -> SavingsAccountTransactionType.INTEREST_POSTING; + case 4 -> SavingsAccountTransactionType.WITHDRAWAL_FEE; + case 5 -> SavingsAccountTransactionType.ANNUAL_FEE; + case 6 -> SavingsAccountTransactionType.WAIVE_CHARGES; + case 7 -> SavingsAccountTransactionType.PAY_CHARGE; + case 8 -> SavingsAccountTransactionType.DIVIDEND_PAYOUT; + case 10 -> SavingsAccountTransactionType.ACCRUAL; + case 12 -> SavingsAccountTransactionType.INITIATE_TRANSFER; + case 13 -> SavingsAccountTransactionType.APPROVE_TRANSFER; + case 14 -> SavingsAccountTransactionType.WITHDRAW_TRANSFER; + case 15 -> SavingsAccountTransactionType.REJECT_TRANSFER; + case 16 -> SavingsAccountTransactionType.WRITTEN_OFF; + case 17 -> SavingsAccountTransactionType.OVERDRAFT_INTEREST; + case 18 -> SavingsAccountTransactionType.WITHHOLD_TAX; + case 19 -> SavingsAccountTransactionType.ESCHEAT; + case 20 -> SavingsAccountTransactionType.AMOUNT_HOLD; + case 21 -> SavingsAccountTransactionType.AMOUNT_RELEASE; + default -> SavingsAccountTransactionType.INVALID; + }; } public boolean isDeposit() { @@ -111,6 +136,10 @@ public boolean isInterestPosting() { return this == INTEREST_POSTING; } + public boolean isAccrual() { + return this.equals(SavingsAccountTransactionType.ACCRUAL); + } + public boolean isOverDraftInterestPosting() { return this == OVERDRAFT_INTEREST; } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java index c9814a96047..6930dcd40bc 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java @@ -48,6 +48,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; @@ -55,6 +56,7 @@ public SavingsAccountTransactionEnumData(final Long id, final String code, final this.value = value; this.deposit = Long.valueOf(SavingsAccountTransactionType.DEPOSIT.getValue()).equals(this.id); this.dividendPayout = Long.valueOf(SavingsAccountTransactionType.DIVIDEND_PAYOUT.getValue()).equals(this.id); + this.accrual = Long.valueOf(SavingsAccountTransactionType.ACCRUAL.getValue()).equals(this.id); this.withdrawal = Long.valueOf(SavingsAccountTransactionType.WITHDRAWAL.getValue()).equals(this.id); this.interestPosting = Long.valueOf(SavingsAccountTransactionType.INTEREST_POSTING.getValue()).equals(this.id); this.feeDeduction = Long.valueOf(SavingsAccountTransactionType.ANNUAL_FEE.getValue()).equals(this.id) diff --git a/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java b/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java index 037f7f96653..b75da01d4ee 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java +++ b/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java @@ -31,7 +31,7 @@ import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams; import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams; import org.apache.fineract.accounting.common.AccountingConstants.SharesProductAccountingParams; -import org.apache.fineract.accounting.common.AccountingRuleType; +import org.apache.fineract.accounting.common.AccountingValidations; import org.apache.fineract.accounting.glaccount.domain.GLAccount; import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.apache.fineract.infrastructure.core.data.ApiParameterError; @@ -84,7 +84,7 @@ public void validateForLoanProductCreate(final String json) { 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)) { final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.FUND_SOURCE.getValue(), element); baseDataValidator.reset().parameter(LoanProductAccountingParams.FUND_SOURCE.getValue()).value(fundAccountId).notNull() @@ -132,7 +132,7 @@ public void validateForLoanProductCreate(final String json) { } - if (isAccrualBasedAccounting(accountingRuleType)) { + if (AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) { final Long receivableInterestAccountId = this.fromApiJsonHelper .extractLongNamed(LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), element); @@ -169,7 +169,7 @@ public void validateForSavingsProductCreate(final String json, DepositAccountTyp Locale.getDefault()); baseDataValidator.reset().parameter(accountingRuleParamName).value(accountingRuleType).notNull().inMinMaxRange(1, 3); - if (isCashBasedAccounting(accountingRuleType)) { + if (AccountingValidations.isCashOrAccrualBasedAccounting(accountingRuleType)) { final Long savingsControlAccountId = this.fromApiJsonHelper .extractLongNamed(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), element); @@ -225,7 +225,24 @@ public void validateForSavingsProductCreate(final String json, DepositAccountTyp baseDataValidator.reset().parameter(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue()).value(writtenOff).notNull() .integerGreaterThanZero(); } + } + + // 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); @@ -247,7 +264,7 @@ public void validateForShareProductCreate(final String json) { 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); @@ -274,15 +291,6 @@ public void validateForShareProductCreate(final String json) { 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 dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", diff --git a/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java index fa9bb087cf6..785e4e6c9b2 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java @@ -25,7 +25,9 @@ import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan; +import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares; @@ -33,6 +35,7 @@ import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingDataParams; import org.apache.fineract.accounting.common.AccountingConstants.SharesProductAccountingParams; import org.apache.fineract.accounting.common.AccountingRuleType; +import org.apache.fineract.accounting.common.AccountingValidations; import org.apache.fineract.accounting.glaccount.data.GLAccountData; import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper; import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; @@ -44,6 +47,7 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; +@Slf4j @Service @RequiredArgsConstructor public class ProductToGLAccountMappingReadPlatformServiceImpl implements ProductToGLAccountMappingReadPlatformService { @@ -116,46 +120,46 @@ public Map fetchAccountMappingDetailsForLoanProduct(final Long l 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); + final GLAccountData glAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); if (glAccountForLoan.equals(CashAccountsForLoan.FUND_SOURCE)) { - accountMappingDetails.put(LoanProductAccountingDataParams.FUND_SOURCE.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.FUND_SOURCE.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_FEES)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_FEES.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_FEES.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_PENALTIES)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_PENALTIES.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_PENALTIES.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.INTEREST_ON_LOANS)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INTEREST_ON_LOANS.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INTEREST_ON_LOANS.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.LOAN_PORTFOLIO)) { - accountMappingDetails.put(LoanProductAccountingDataParams.LOAN_PORTFOLIO.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.LOAN_PORTFOLIO.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.TRANSFERS_SUSPENSE)) { - accountMappingDetails.put(LoanProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.LOSSES_WRITTEN_OFF)) { - accountMappingDetails.put(LoanProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.GOODWILL_CREDIT)) { - accountMappingDetails.put(LoanProductAccountingDataParams.GOODWILL_CREDIT.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.GOODWILL_CREDIT.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.OVERPAYMENT)) { - accountMappingDetails.put(LoanProductAccountingDataParams.OVERPAYMENT.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.OVERPAYMENT.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_RECOVERY)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_RECOVERY.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_RECOVERY.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.CHARGE_OFF_EXPENSE)) { - accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_EXPENSE.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_EXPENSE.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE)) { - accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_INTEREST)) { accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(), - gLAccountData); + glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_FEES)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(), glAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_PENALTY)) { accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(), - gLAccountData); + glAccountData); } } @@ -169,52 +173,52 @@ public Map fetchAccountMappingDetailsForLoanProduct(final Long l 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); + final GLAccountData glAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); if (glAccountForLoan.equals(AccrualAccountsForLoan.FUND_SOURCE)) { - accountMappingDetails.put(LoanProductAccountingDataParams.FUND_SOURCE.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.FUND_SOURCE.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_FEES)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_FEES.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_FEES.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_PENALTIES)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_PENALTIES.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_PENALTIES.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INTEREST_ON_LOANS)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INTEREST_ON_LOANS.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INTEREST_ON_LOANS.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.LOAN_PORTFOLIO)) { - accountMappingDetails.put(LoanProductAccountingDataParams.LOAN_PORTFOLIO.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.LOAN_PORTFOLIO.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.OVERPAYMENT)) { - accountMappingDetails.put(LoanProductAccountingDataParams.OVERPAYMENT.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.OVERPAYMENT.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.TRANSFERS_SUSPENSE)) { - accountMappingDetails.put(LoanProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.LOSSES_WRITTEN_OFF)) { - accountMappingDetails.put(LoanProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.GOODWILL_CREDIT)) { - accountMappingDetails.put(LoanProductAccountingDataParams.GOODWILL_CREDIT.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.GOODWILL_CREDIT.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INTEREST_RECEIVABLE)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INTEREST_RECEIVABLE.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INTEREST_RECEIVABLE.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.FEES_RECEIVABLE)) { - accountMappingDetails.put(LoanProductAccountingDataParams.FEES_RECEIVABLE.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.FEES_RECEIVABLE.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.PENALTIES_RECEIVABLE)) { - accountMappingDetails.put(LoanProductAccountingDataParams.PENALTIES_RECEIVABLE.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.PENALTIES_RECEIVABLE.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_RECOVERY)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_RECOVERY.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_RECOVERY.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_FEES)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_FEES.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_INTEREST)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_INTEREST.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.CHARGE_OFF_EXPENSE)) { - accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_EXPENSE.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_EXPENSE.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE)) { - accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_CHARGE_OFF_PENALTY)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CHARGE_OFF_PENALTY.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_INTEREST)) { accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_INTEREST.getValue(), - gLAccountData); + glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_FEES)) { - accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(), gLAccountData); + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_FEES.getValue(), glAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_GOODWILL_CREDIT_PENALTY)) { accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_GOODWILL_CREDIT_PENALTY.getValue(), - gLAccountData); + glAccountData); } } @@ -225,7 +229,7 @@ public Map fetchAccountMappingDetailsForLoanProduct(final Long l @Override public Map fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType) { - final Map accountMappingDetails = new LinkedHashMap<>(8); + Map 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 "; @@ -233,41 +237,38 @@ public Map fetchAccountMappingDetailsForSavingsProduct(final Lon final List> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, // NOSONAR new Object[] { PortfolioProductType.SAVING.getValue(), savingsProductId }); - if (AccountingRuleType.CASH_BASED.getValue().equals(accountingType)) { + accountMappingDetails = setBaseSavingsProductToGLAccountMaps(listOfProductToGLAccountMaps); + + // Set additional GL Accounting Maps for Accrual + if (AccountingValidations.isAccrualPeriodicBasedAccounting(accountingType)) { for (final Map productToGLAccountMap : 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); + 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.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.INTEREST_PAYABLE)) { + accountMappingDetails.put(SavingProductAccountingDataParams.INTEREST_PAYABLE.getValue(), glAccountData); + } + } else { + log.error("Accounting mapping null {}", financialAccountType); } } + } + return accountMappingDetails; } @@ -305,10 +306,10 @@ private List fetchPaymentTypeToFundSourceMappings( 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); + final GLAccountData glAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); final PaymentTypeToGLAccountMapper paymentTypeToGLAccountMapper = new PaymentTypeToGLAccountMapper() - .setPaymentType(paymentTypeData).setFundSourceAccount(gLAccountData); + .setPaymentType(paymentTypeData).setFundSourceAccount(glAccountData); paymentTypeToGLAccountMappers.add(paymentTypeToGLAccountMapper); } return paymentTypeToGLAccountMappers; @@ -354,13 +355,13 @@ private List fetchChargeToIncomeAccountMappings(final P final Long glAccountId = (Long) chargeToIncomeAccountMap.get("glAccountId"); final String glAccountName = (String) chargeToIncomeAccountMap.get("glAccountName"); final String glCode = (String) chargeToIncomeAccountMap.get("glCode"); - final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); + final GLAccountData glAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); final Long chargeId = (Long) chargeToIncomeAccountMap.get("chargeId"); final String chargeName = (String) chargeToIncomeAccountMap.get("chargeName"); final Boolean penalty1 = (Boolean) chargeToIncomeAccountMap.get("penalty"); final ChargeData chargeData = ChargeData.lookup(chargeId, chargeName, penalty1); final ChargeToGLAccountMapper chargeToGLAccountMapper = new ChargeToGLAccountMapper().setCharge(chargeData) - .setIncomeAccount(gLAccountData); + .setIncomeAccount(glAccountData); chargeToGLAccountMappers.add(chargeToGLAccountMapper); } return chargeToGLAccountMappers; @@ -385,16 +386,16 @@ public Map fetchAccountMappingDetailsForShareProduct(Long produc 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); + final GLAccountData glAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); if (glAccountForShares.equals(CashAccountsForShares.SHARES_REFERENCE)) { - accountMappingDetails.put(SharesProductAccountingParams.SHARES_REFERENCE.getValue(), gLAccountData); + accountMappingDetails.put(SharesProductAccountingParams.SHARES_REFERENCE.getValue(), glAccountData); } else if (glAccountForShares.equals(CashAccountsForShares.SHARES_SUSPENSE)) { - accountMappingDetails.put(SharesProductAccountingParams.SHARES_SUSPENSE.getValue(), gLAccountData); + accountMappingDetails.put(SharesProductAccountingParams.SHARES_SUSPENSE.getValue(), glAccountData); } else if (glAccountForShares.equals(CashAccountsForShares.INCOME_FROM_FEES)) { - accountMappingDetails.put(SharesProductAccountingParams.INCOME_FROM_FEES.getValue(), gLAccountData); + accountMappingDetails.put(SharesProductAccountingParams.INCOME_FROM_FEES.getValue(), glAccountData); } else if (glAccountForShares.equals(CashAccountsForShares.SHARES_EQUITY)) { - accountMappingDetails.put(SharesProductAccountingParams.SHARES_EQUITY.getValue(), gLAccountData); + accountMappingDetails.put(SharesProductAccountingParams.SHARES_EQUITY.getValue(), glAccountData); } } } @@ -412,4 +413,51 @@ public List fetchFeeToIncomeAccountMappingsForShareProd return fetchChargeToIncomeAccountMappings(PortfolioProductType.SHARES, productId, false); } + private Map setBaseSavingsProductToGLAccountMaps(final List> listOfProductToGLAccountMaps) { + final Map accountMappingDetails = new LinkedHashMap<>(8); + + for (final Map productToGLAccountMap : listOfProductToGLAccountMaps) { + + final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType"); + CashAccountsForSavings glAccountForSavings = CashAccountsForSavings.fromInt(financialAccountType); + + if (glAccountForSavings != null) { + final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId"); + final String glAccountName = (String) productToGLAccountMap.get("glAccountName"); + final String glCode = (String) productToGLAccountMap.get("glCode"); + final GLAccountData glAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); + + // Assets + if (glAccountForSavings.equals(CashAccountsForSavings.SAVINGS_REFERENCE)) { + accountMappingDetails.put(SavingProductAccountingDataParams.SAVINGS_REFERENCE.getValue(), glAccountData); + } else if (glAccountForSavings.equals(CashAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL)) { + accountMappingDetails.put(SavingProductAccountingDataParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), glAccountData); + // Liabilities + } else if (glAccountForSavings.equals(CashAccountsForSavings.SAVINGS_CONTROL)) { + accountMappingDetails.put(SavingProductAccountingDataParams.SAVINGS_CONTROL.getValue(), glAccountData); + } else if (glAccountForSavings.equals(CashAccountsForSavings.TRANSFERS_SUSPENSE)) { + accountMappingDetails.put(SavingProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), glAccountData); + // Income + } else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_FEES)) { + accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_FEES.getValue(), glAccountData); + } else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_PENALTIES)) { + accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_PENALTIES.getValue(), glAccountData); + } else if (glAccountForSavings.equals(CashAccountsForSavings.INCOME_FROM_INTEREST)) { + accountMappingDetails.put(SavingProductAccountingDataParams.INCOME_FROM_INTEREST.getValue(), glAccountData); + } else if (glAccountForSavings.equals(CashAccountsForSavings.ESCHEAT_LIABILITY)) { + accountMappingDetails.put(SavingProductAccountingDataParams.ESCHEAT_LIABILITY.getValue(), glAccountData); + // Expense + } else if (glAccountForSavings.equals(CashAccountsForSavings.INTEREST_ON_SAVINGS)) { + accountMappingDetails.put(SavingProductAccountingDataParams.INTEREST_ON_SAVINGS.getValue(), glAccountData); + } else if (glAccountForSavings.equals(CashAccountsForSavings.LOSSES_WRITTEN_OFF)) { + accountMappingDetails.put(SavingProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), glAccountData); + } + } else { + log.error("Accounting mapping null {}", financialAccountType); + } + } + + return accountMappingDetails; + } + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java index ad957c5d08b..d10dd7de88b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -27,6 +27,7 @@ import java.util.Map; import lombok.RequiredArgsConstructor; import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan; +import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares; @@ -208,6 +209,65 @@ public void createLoanProductToGLAccountMapping(final Long loanProductId, final } } + 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, @@ -216,67 +276,28 @@ public void createSavingProductToGLAccountMapping(final Long savingProductId, fi 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 - 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()); - } + saveSavingsBaseAccountMapping(savingProductId, accountType, command, element); + break; - // expenses - this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element, - SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), savingProductId, - CashAccountsForSavings.INTEREST_ON_SAVINGS.getValue()); + case ACCRUAL_PERIODIC: + saveSavingsBaseAccountMapping(savingProductId, accountType, command, element); + // assets + this.savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element, + SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), savingProductId, + AccrualAccountsForSavings.FEES_RECEIVABLE.getValue()); - if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) { - this.savingsProductToGLAccountMappingHelper.saveSavingsToExpenseAccountMapping(element, - SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), savingProductId, - CashAccountsForSavings.LOSSES_WRITTEN_OFF.getValue()); - } + this.savingsProductToGLAccountMappingHelper.saveSavingsToAssetAccountMapping(element, + SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), savingProductId, + AccrualAccountsForSavings.PENALTIES_RECEIVABLE.getValue()); // liability this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element, - SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), savingProductId, - CashAccountsForSavings.SAVINGS_CONTROL.getValue()); - this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element, - SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), savingProductId, - CashAccountsForSavings.TRANSFERS_SUSPENSE.getValue()); - - final Boolean isDormancyTrackingActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, - element); - if (null != isDormancyTrackingActive && isDormancyTrackingActive) { - this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element, - SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), savingProductId, - CashAccountsForSavings.ESCHEAT_LIABILITY.getValue()); - } - - // advanced accounting mappings - this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null); - this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null); + SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), savingProductId, + AccrualAccountsForSavings.INTEREST_PAYABLE.getValue()); break; default: break; diff --git a/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java b/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java index 6cccdfe1f19..4b3e75b519d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java +++ b/fineract-loan/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/SavingsProductToGLAccountMappingHelper.java @@ -21,6 +21,7 @@ import com.google.gson.JsonElement; import java.util.HashMap; import java.util.Map; +import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings; import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams; import org.apache.fineract.accounting.common.AccountingRuleType; @@ -167,6 +168,25 @@ public Map populateChangesForNewSavingsProductToGLAccountMapping 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 void handleChangesToSavingsProductToGLAccountMappings(final Long savingsP savingsProductId, CashAccountsForSavings.ESCHEAT_LIABILITY.getValue(), changes); break; case ACCRUAL_PERIODIC: + // asset + mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), + savingsProductId, AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), + AccrualAccountsForSavings.SAVINGS_REFERENCE.toString(), changes); + + mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), + savingsProductId, AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), + AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.toString(), changes); + + mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), + savingsProductId, AccrualAccountsForSavings.FEES_RECEIVABLE.getValue(), + AccrualAccountsForSavings.FEES_RECEIVABLE.toString(), changes); + + mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), + savingsProductId, AccrualAccountsForSavings.PENALTIES_RECEIVABLE.getValue(), + AccrualAccountsForSavings.PENALTIES_RECEIVABLE.toString(), changes); + + // income + mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), + savingsProductId, AccrualAccountsForSavings.INCOME_FROM_FEES.getValue(), + AccrualAccountsForSavings.INCOME_FROM_FEES.toString(), changes); + + mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), + savingsProductId, AccrualAccountsForSavings.INCOME_FROM_PENALTIES.getValue(), + AccrualAccountsForSavings.INCOME_FROM_PENALTIES.toString(), changes); + + mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), + savingsProductId, AccrualAccountsForSavings.INCOME_FROM_INTEREST.getValue(), + AccrualAccountsForSavings.INCOME_FROM_INTEREST.toString(), changes); + + // expenses + mergeSavingsToExpenseAccountMappingChanges(element, SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), + savingsProductId, AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(), + AccrualAccountsForSavings.INTEREST_ON_SAVINGS.toString(), changes); + + mergeSavingsToExpenseAccountMappingChanges(element, SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), + savingsProductId, AccrualAccountsForSavings.LOSSES_WRITTEN_OFF.getValue(), + AccrualAccountsForSavings.LOSSES_WRITTEN_OFF.toString(), changes); + + // liability + mergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), + savingsProductId, AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), + AccrualAccountsForSavings.SAVINGS_CONTROL.toString(), changes); + + mergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), + savingsProductId, AccrualAccountsForSavings.TRANSFERS_SUSPENSE.getValue(), + AccrualAccountsForSavings.TRANSFERS_SUSPENSE.toString(), changes); + + mergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), + savingsProductId, AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(), + AccrualAccountsForSavings.INTEREST_PAYABLE.toString(), changes); + + createOrmergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), + savingsProductId, AccrualAccountsForSavings.ESCHEAT_LIABILITY.getValue(), changes); + break; case ACCRUAL_UPFRONT: break; diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java index e5c9fb4b5f3..5bccce4e264 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorForSavingsFactory.java @@ -40,6 +40,9 @@ public AccountingProcessorForSavings determineProcessor(final SavingsDTO savings if (savingsDTO.isCashBasedAccountingEnabled()) { accountingProcessorForSavings = this.applicationContext.getBean("cashBasedAccountingProcessorForSavings", AccountingProcessorForSavings.class); + } else if (savingsDTO.isAccrualBasedAccountingEnabled()) { + accountingProcessorForSavings = this.applicationContext.getBean("accrualBasedAccountingProcessorForSavings", + AccountingProcessorForSavings.class); } return accountingProcessorForSavings; diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java index 2aa882c6f05..19b059bd19e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java @@ -30,6 +30,7 @@ import org.apache.fineract.accounting.closure.domain.GLClosure; import org.apache.fineract.accounting.closure.domain.GLClosureRepository; import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan; +import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForLoan; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings; import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares; @@ -577,6 +578,47 @@ public void createCashBasedJournalEntriesAndReversalsForSavingsTax(final Office paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); } + /** + * Convenience method that creates a pair of related Debits and Credits for Periodic Accrual Based accounting. + * + * The target accounts for debits and credits are switched in case of a reversal + * + * @param office + * @param currencyCode + * @param accountTypeToBeDebited + * Enum of the placeholder GLAccount to be debited + * @param accountTypeToBeCredited + * Enum of the placeholder of the GLAccount to be credited + * @param savingsProductId + * @param paymentTypeId + * @param savingsId + * @param transactionId + * @param transactionDate + * @param amount + * @param isReversal + * @param taxDetails + */ + 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 taxDetails) { + + for (TaxPaymentDTO taxPaymentDTO : taxDetails) { + if (taxPaymentDTO.getAmount() != null) { + if (taxPaymentDTO.getCreditAccountId() == null) { + createCashBasedCreditJournalEntriesAndReversalsForSavings(office, currencyCode, accountTypeToBeCredited.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, taxPaymentDTO.getAmount(), + isReversal); + } else { + createCashBasedCreditJournalEntriesAndReversalsForSavings(office, currencyCode, taxPaymentDTO.getCreditAccountId(), + savingsId, transactionId, transactionDate, taxPaymentDTO.getAmount(), isReversal); + } + } + } + createCashBasedDebitJournalEntriesAndReversalsForSavings(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) { @@ -772,6 +814,71 @@ public void createCashBasedJournalEntriesAndReversalsForSavingsCharges(final Off } } + /** + * Convenience method that creates a pair of related Debits and Credits for Accrual Periodic Based accounting. + * + * The target accounts for debits and credits are switched in case of a reversal + * + * @param office + * office + * @param currencyCode + * currencyCode + * @param accountTypeToBeDebited + * Enum of the placeholder GLAccount to be debited + * @param accountTypeToBeCredited + * Enum of the placeholder of the GLAccount to be credited + * @param savingsProductId + * savingsProductId + * @param paymentTypeId + * paymentTypeId + * @param loanId + * loanId + * @param transactionId + * transactionId + * @param transactionDate + * transactionDate + * @param totalAmount + * totalAmount + * @param isReversal + * isReversal + * @param chargePaymentDTOs + * chargePaymentDTOs + */ + 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 chargePaymentDTOs) { + // TODO Vishwas: Remove this validation, as and when appropriate Junit + // tests are written for accounting + /** + * Accounting module currently supports a single charge per transaction, throw an error if this is not the case + * here so any developers changing the expected portfolio behavior would also take care of modifying the + * accounting code appropriately + **/ + if (chargePaymentDTOs.size() != 1) { + throw new PlatformDataIntegrityException("Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code", + "Recent Portfolio changes w.r.t Charges for Savings have Broken the accounting code"); + } + ChargePaymentDTO chargePaymentDTO = chargePaymentDTOs.get(0); + GLAccount chargeSpecificAccount = getLinkedGLAccountForSavingsCharges(savingsProductId, accountTypeToBeCredited.getValue(), + chargePaymentDTO.getChargeId()); + + final GLAccount savingsControlAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToBeDebited.getValue(), + paymentTypeId); + if (isReversal) { + createDebitJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate, + totalAmount); + createCreditJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate, + totalAmount); + } else { + createDebitJournalEntryForSavings(office, currencyCode, savingsControlAccount, loanId, transactionId, transactionDate, + totalAmount); + createCreditJournalEntryForSavings(office, currencyCode, chargeSpecificAccount, loanId, transactionId, transactionDate, + totalAmount); + } + } + public LoanTransaction getLoanTransactionById(final Long loanTransactionId) { return this.loanTransactionRepository.getReferenceById(loanTransactionId); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java new file mode 100644 index 00000000000..4aa1b935bd8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java @@ -0,0 +1,272 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.accounting.journalentry.service; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.accounting.closure.domain.GLClosure; +import org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForSavings; +import org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity; +import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO; +import org.apache.fineract.accounting.journalentry.data.SavingsDTO; +import org.apache.fineract.accounting.journalentry.data.SavingsTransactionDTO; +import org.apache.fineract.organisation.office.domain.Office; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AccrualBasedAccountingProcessorForSavings implements AccountingProcessorForSavings { + + private final AccountingProcessorHelper helper; + + @Override + public void createJournalEntriesForSavings(final SavingsDTO savingsDTO) { + final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(savingsDTO.getOfficeId()); + final Long savingsProductId = savingsDTO.getSavingsProductId(); + final Long savingsId = savingsDTO.getSavingsId(); + final String currencyCode = savingsDTO.getCurrencyCode(); + for (final SavingsTransactionDTO savingsTransactionDTO : savingsDTO.getNewSavingsTransactions()) { + final LocalDate transactionDate = savingsTransactionDTO.getTransactionDate(); + final String transactionId = savingsTransactionDTO.getTransactionId(); + final Office office = this.helper.getOfficeById(savingsTransactionDTO.getOfficeId()); + final Long paymentTypeId = savingsTransactionDTO.getPaymentTypeId(); + final boolean isReversal = savingsTransactionDTO.isReversed(); + final BigDecimal amount = savingsTransactionDTO.getAmount(); + final BigDecimal overdraftAmount = savingsTransactionDTO.getOverdraftAmount(); + final List feePayments = savingsTransactionDTO.getFeePayments(); + final List penaltyPayments = savingsTransactionDTO.getPenaltyPayments(); + + this.helper.checkForBranchClosures(latestGLClosure, transactionDate); + + if (savingsTransactionDTO.getTransactionType().isWithdrawal() && savingsTransactionDTO.isOverdraftTransaction()) { + boolean isPositive = amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) > 0; + if (savingsTransactionDTO.isAccountTransfer()) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), + FinancialActivity.LIABILITY_TRANSFER.getValue(), savingsProductId, paymentTypeId, savingsId, transactionId, + transactionDate, overdraftAmount, isReversal); + if (isPositive) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), FinancialActivity.LIABILITY_TRANSFER.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, + amount.subtract(overdraftAmount), isReversal); + } + } else { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), + AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, overdraftAmount, isReversal); + if (isPositive) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), + AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal); + } + } + } + + else if (savingsTransactionDTO.getTransactionType().isDeposit() && savingsTransactionDTO.isOverdraftTransaction()) { + boolean isPositive = amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) > 0; + if (savingsTransactionDTO.isAccountTransfer()) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + FinancialActivity.LIABILITY_TRANSFER.getValue(), + AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, overdraftAmount, isReversal); + if (isPositive) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + FinancialActivity.LIABILITY_TRANSFER.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, + amount.subtract(overdraftAmount), isReversal); + } + } else { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), + AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, overdraftAmount, isReversal); + if (isPositive) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), + AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal); + } + } + } + + /** Handle Deposits and reversals of deposits **/ + else if (savingsTransactionDTO.getTransactionType().isDeposit()) { + if (savingsTransactionDTO.isAccountTransfer()) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + FinancialActivity.LIABILITY_TRANSFER.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + } else { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + } + } + + /** Handle Deposits and reversals of Dividend pay outs **/ + else if (savingsTransactionDTO.getTransactionType().isDividendPayout()) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + FinancialActivity.PAYABLE_DIVIDENDS.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + } + + /** Handle withdrawals and reversals of withdrawals **/ + else if (savingsTransactionDTO.getTransactionType().isWithdrawal()) { + if (savingsTransactionDTO.isAccountTransfer()) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), FinancialActivity.LIABILITY_TRANSFER.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + } else { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + } + } + + else if (savingsTransactionDTO.getTransactionType().isEscheat()) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), AccrualAccountsForSavings.ESCHEAT_LIABILITY.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + } + /** + * Handle Interest Applications and reversals of Interest Applications + **/ + else if (savingsTransactionDTO.getTransactionType().isInterestPosting() && savingsTransactionDTO.isOverdraftTransaction()) { + boolean isPositive = amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) > 0; + // Post journal entry if earned interest amount is greater than + // zero + if (savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO) > 0) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(), + AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, overdraftAmount, isReversal); + if (isPositive) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(), + AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal); + } + } + } + + else if (savingsTransactionDTO.getTransactionType().isInterestPosting()) { + // Post journal entry if earned interest amount is greater than + // zero + if (savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO) > 0) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + } + } + + else if (savingsTransactionDTO.getTransactionType().isAccrual()) { + // Post journal entry for Accrual Recognition + if (savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO) > 0) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(), AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + } + } + + else if (savingsTransactionDTO.getTransactionType().isWithholdTax()) { + this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsTax(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_CONTROL, AccrualAccountsForSavings.SAVINGS_REFERENCE, savingsProductId, + paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, + savingsTransactionDTO.getTaxPayments()); + } + + /** Handle Fees Deductions and reversals of Fees Deductions **/ + else if (savingsTransactionDTO.getTransactionType().isFeeDeduction() && savingsTransactionDTO.isOverdraftTransaction()) { + boolean isPositive = amount.subtract(overdraftAmount).compareTo(BigDecimal.ZERO) > 0; + // Is the Charge a penalty? + if (penaltyPayments.size() > 0) { + this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, + AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL, AccrualAccountsForSavings.INCOME_FROM_PENALTIES, + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, overdraftAmount, isReversal, + penaltyPayments); + if (isPositive) { + this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_CONTROL, AccrualAccountsForSavings.INCOME_FROM_PENALTIES, + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, + amount.subtract(overdraftAmount), isReversal, penaltyPayments); + } + } else { + this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, + AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL, AccrualAccountsForSavings.INCOME_FROM_FEES, + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, overdraftAmount, isReversal, + feePayments); + if (isPositive) { + this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_CONTROL, AccrualAccountsForSavings.INCOME_FROM_FEES, savingsProductId, + paymentTypeId, savingsId, transactionId, transactionDate, amount.subtract(overdraftAmount), isReversal, + feePayments); + } + } + } + + else if (savingsTransactionDTO.getTransactionType().isFeeDeduction()) { + // Is the Charge a penalty? + if (penaltyPayments.size() > 0) { + this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_CONTROL, AccrualAccountsForSavings.INCOME_FROM_PENALTIES, savingsProductId, + paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, penaltyPayments); + } else { + this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_CONTROL, AccrualAccountsForSavings.INCOME_FROM_FEES, savingsProductId, + paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, feePayments); + } + } + + /** Handle Transfers proposal **/ + else if (savingsTransactionDTO.getTransactionType().isInitiateTransfer()) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), AccrualAccountsForSavings.TRANSFERS_SUSPENSE.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + } + + /** Handle Transfer Withdrawal or Acceptance **/ + else if (savingsTransactionDTO.getTransactionType().isWithdrawTransfer() + || savingsTransactionDTO.getTransactionType().isApproveTransfer()) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.TRANSFERS_SUSPENSE.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + } + + /** overdraft **/ + else if (savingsTransactionDTO.getTransactionType().isOverdraftInterest()) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_REFERENCE.getValue(), AccrualAccountsForSavings.INCOME_FROM_INTEREST.getValue(), + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + } else if (savingsTransactionDTO.getTransactionType().isWrittenoff()) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, + AccrualAccountsForSavings.LOSSES_WRITTEN_OFF.getValue(), + AccrualAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, amount, isReversal); + } else if (savingsTransactionDTO.getTransactionType().isOverdraftFee()) { + this.helper.createAccrualBasedJournalEntriesAndReversalsForSavingsCharges(office, currencyCode, + AccrualAccountsForSavings.SAVINGS_REFERENCE, AccrualAccountsForSavings.INCOME_FROM_FEES, savingsProductId, + paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, feePayments); + } + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java index c6769617b37..0767289767e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -33,10 +33,9 @@ import java.util.Map; import java.util.Set; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams; -import org.apache.fineract.accounting.common.AccountingRuleType; +import org.apache.fineract.accounting.common.AccountingValidations; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; @@ -58,7 +57,6 @@ import org.apache.fineract.portfolio.loanproduct.exception.EqualAmortizationUnsupportedFeatureException; import org.springframework.stereotype.Component; -@Slf4j @RequiredArgsConstructor @Component public final class LoanProductDataValidator { @@ -600,7 +598,7 @@ public void validateForCreate(final String json) { 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.isCashOrAccrualBasedAccounting(accountingRuleType)) { final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.FUND_SOURCE.getValue(), element); baseDataValidator.reset().parameter(LoanProductAccountingParams.FUND_SOURCE.getValue()).value(fundAccountId).notNull() @@ -696,7 +694,7 @@ public void validateForCreate(final String json) { } - if (isAccrualBasedAccounting(accountingRuleType)) { + if (AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) { final Long receivableInterestAccountId = this.fromApiJsonHelper .extractLongNamed(LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), element); @@ -2021,22 +2019,6 @@ private void validateNominalInterestRatePerPeriodMinMaxConstraint(final JsonElem } } - 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 dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java index 9fe796d21bf..c8f1b4f9c34 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java @@ -318,88 +318,31 @@ private GetFixedDepositProductsProductIdInterestCompoundingPeriodType() {} public String description; } - static final class GetFixedDepositProductsProductIdAccountingMappings { - - private GetFixedDepositProductsProductIdAccountingMappings() {} - - static final class GetFixedDepositProductsProductIdSavingsReferenceAccount { - - private GetFixedDepositProductsProductIdSavingsReferenceAccount() {} - - @Schema(example = "12") - public Integer id; - @Schema(example = "savings ref") - public String name; - @Schema(example = "20") - public Integer glCode; - } - - static final class GetFixedDepositProductsProductIdIncomeFromFeeAccount { - - private GetFixedDepositProductsProductIdIncomeFromFeeAccount() {} + static final class GetFixedDepositProductsGlAccount { - @Schema(example = "16") - public Integer id; - @Schema(example = "income from savings fee") - public String name; - @Schema(example = "24") - public Integer glCode; - } - - static final class GetFixedDepositProductsProductIdIncomeFromPenaltyAccount { - - private GetFixedDepositProductsProductIdIncomeFromPenaltyAccount() {} - - @Schema(example = "17") - public Integer id; - @Schema(example = "income from sav penalties") - public String name; - @Schema(example = "25") - public Integer glCode; - } + private GetFixedDepositProductsGlAccount() {} - static final class GetFixedDepositProductsProductIdInterestOnSavingsAccount { - - private GetFixedDepositProductsProductIdInterestOnSavingsAccount() {} - - @Schema(example = "15") - public Integer id; - @Schema(example = "interest on savings") - public String name; - @Schema(example = "23") - public Integer glCode; - } - - static final class GetFixedDepositProductsProductIdSavingsControlAccount { - - private GetFixedDepositProductsProductIdSavingsControlAccount() {} - - @Schema(example = "13") - public Integer id; - @Schema(example = "savings ref tool kit") - public String name; - @Schema(example = "21") - public Integer glCode; - } - - static final class GetFixedDepositProductsProductIdTransfersInSuspenseAccount { + @Schema(example = "12") + public Integer id; + @Schema(example = "savings ref") + public String name; + @Schema(example = "20") + public Integer glCode; + } - private GetFixedDepositProductsProductIdTransfersInSuspenseAccount() {} + static final class GetFixedDepositProductsProductIdAccountingMappings { - @Schema(example = "14") - public Integer id; - @Schema(example = "saving transfers") - public String name; - @Schema(example = "22") - public Integer glCode; - } + private GetFixedDepositProductsProductIdAccountingMappings() {} - 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 { @@ -420,20 +363,8 @@ private GetFixedDepositProductsProductIdFeeToIncomeAccountMappingsCharge() {} public Boolean penalty; } - static final class GetFixedDepositProductsProductIdFeeToIncomeAccountMappingsIncomeAccount { - - private GetFixedDepositProductsProductIdFeeToIncomeAccountMappingsIncomeAccount() {} - - @Schema(example = "16") - public Integer id; - @Schema(example = "income from savings fee") - public String name; - @Schema(example = "24") - public Integer glCode; - } - public GetFixedDepositProductsProductIdFeeToIncomeAccountMappingsCharge charge; - public GetFixedDepositProductsProductIdFeeToIncomeAccountMappingsIncomeAccount incomeAccount; + public GetFixedDepositProductsGlAccount incomeAccount; } static final class GetFixedDepositProductsProductIdPenaltyToIncomeAccountMappings { @@ -455,7 +386,7 @@ private GetFixedDepositProductsProductIdPenaltyToIncomeAccountMappingsCharge() { } public GetFixedDepositProductsProductIdPenaltyToIncomeAccountMappingsCharge charge; - public GetFixedDepositProductsProductIdAccountingMappings.GetFixedDepositProductsProductIdIncomeFromPenaltyAccount incomeAccount; + public GetFixedDepositProductsGlAccount incomeAccount; } static final class GetFixedDepositProductsProductIdPreClosurePenalInterestOnType { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java index 8a1a593266d..02622104a99 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java @@ -343,88 +343,30 @@ private GetRecurringDepositProductsProductIdInterestCompoundingPeriodType() {} public String description; } - static final class GetRecurringDepositProductsProductIdAccountingMappings { - - private GetRecurringDepositProductsProductIdAccountingMappings() {} - - static final class GetRecurringDepositProductsProductIdSavingsReferenceAccount { - - private GetRecurringDepositProductsProductIdSavingsReferenceAccount() {} - - @Schema(example = "12") - public Integer id; - @Schema(example = "savings ref") - public String name; - @Schema(example = "20") - public Integer glCode; - } - - static final class GetRecurringDepositProductsProductIdIncomeFromFeeAccount { - - private GetRecurringDepositProductsProductIdIncomeFromFeeAccount() {} - - @Schema(example = "16") - public Integer id; - @Schema(example = "income from savings fee") - public String name; - @Schema(example = "24") - public Integer glCode; - } - - static final class GetRecurringDepositProductsProductIdIncomeFromPenaltyAccount { - - private GetRecurringDepositProductsProductIdIncomeFromPenaltyAccount() {} - - @Schema(example = "17") - public Integer id; - @Schema(example = "income from sav penalties") - public String name; - @Schema(example = "25") - public Integer glCode; - } - - static final class GetRecurringDepositProductsProductIdInterestOnSavingsAccount { + static final class GetRecurringDepositProductsGlAccount { - private GetRecurringDepositProductsProductIdInterestOnSavingsAccount() {} + private GetRecurringDepositProductsGlAccount() {} - @Schema(example = "15") - public Integer id; - @Schema(example = "interest on savings") - public String name; - @Schema(example = "23") - public Integer glCode; - } - - static final class GetRecurringDepositProductsProductIdSavingsControlAccount { - - private GetRecurringDepositProductsProductIdSavingsControlAccount() {} - - @Schema(example = "13") - public Integer id; - @Schema(example = "savings ref tool kit") - public String name; - @Schema(example = "21") - public Integer glCode; - } - - static final class GetRecurringDepositProductsProductIdTransfersInSuspenseAccount { + @Schema(example = "12") + public Integer id; + @Schema(example = "savings control") + public String name; + @Schema(example = "2000001") + public String glCode; + } - private GetRecurringDepositProductsProductIdTransfersInSuspenseAccount() {} + static final class GetRecurringDepositProductsProductIdAccountingMappings { - @Schema(example = "14") - public Integer id; - @Schema(example = "saving transfers") - public String name; - @Schema(example = "22") - public Integer glCode; - } + private GetRecurringDepositProductsProductIdAccountingMappings() {} - 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 @@ private GetRecurringDepositProductsProductIdPenaltyToIncomeAccountMappingsCharge } public GetRecurringDepositProductsProductIdPenaltyToIncomeAccountMappingsCharge charge; - public GetRecurringDepositProductsProductIdAccountingMappings.GetRecurringDepositProductsProductIdIncomeFromPenaltyAccount incomeAccount; + public GetRecurringDepositProductsGlAccount incomeAccount; } static final class GetRecurringDepositProductsProductIdPreClosurePenalInterestOnType { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java index 5aaeabbc0da..2f9137f5483 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java @@ -226,88 +226,34 @@ public static final class GetSavingsProductsProductIdResponse { private GetSavingsProductsProductIdResponse() {} - 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 Integer 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 Integer 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 Integer glCode; - } - - static final class GetSavingsProductsInterestOnSavingsAccount { + static final class GetSavingsProductsGlAccount { - private GetSavingsProductsInterestOnSavingsAccount() {} - - @Schema(example = "15") - public Integer id; - @Schema(example = "interest on savings") - public String name; - @Schema(example = "23") - public Integer glCode; - } + private GetSavingsProductsGlAccount() {} - 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 Integer glCode; - } - - static final class GetSavingsProductsTransfersInSuspenseAccount { + @Schema(example = "12") + public Integer id; + @Schema(example = "savings control") + public String name; + @Schema(example = "2000001") + public String glCode; + } - private GetSavingsProductsTransfersInSuspenseAccount() {} + static final class GetSavingsProductsAccountingMappings { - @Schema(example = "14") - public Integer id; - @Schema(example = "saving transfers") - public String name; - @Schema(example = "22") - public Integer glCode; - } + private GetSavingsProductsAccountingMappings() {} - 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 { @@ -324,20 +270,8 @@ private GetSavingsProductsPaymentType() {} public String name; } - static final class GetSavingsProductsFundSourceAccount { - - private GetSavingsProductsFundSourceAccount() {} - - @Schema(example = "12") - public Integer id; - @Schema(example = "savings ref") - public String name; - @Schema(example = "20") - public Integer glCode; - } - public GetSavingsProductsPaymentType paymentType; - public GetSavingsProductsFundSourceAccount fundSourceAccount; + public GetSavingsProductsGlAccount fundSourceAccount; } static final class GetSavingsProductsFeeToIncomeAccountMappings { @@ -358,20 +292,8 @@ private GetSavingsProductsFeeToIncomeAccountMappingsCharge() {} public Boolean penalty; } - static final class GetSavingsProductsFeeToIncomeAccountMappingsIncomeAccount { - - private GetSavingsProductsFeeToIncomeAccountMappingsIncomeAccount() {} - - @Schema(example = "16") - public Integer id; - @Schema(example = "income from savings fee") - public String name; - @Schema(example = "24") - public Integer glCode; - } - public GetSavingsProductsFeeToIncomeAccountMappingsCharge charge; - public GetSavingsProductsFeeToIncomeAccountMappingsIncomeAccount incomeAccount; + public GetSavingsProductsGlAccount incomeAccount; } static final class GetSavingsProductsPenaltyToIncomeAccountMappings { @@ -393,7 +315,7 @@ private GetSavingsProductsPenaltyToIncomeAccountMappingsCharge() {} } public GetSavingsProductsPenaltyToIncomeAccountMappingsCharge charge; - public GetSavingsProductsAccountingMappings.GetSavingsProductsIncomeFromPenaltyAccount incomeAccount; + public GetSavingsProductsGlAccount incomeAccount; } @Schema(example = "1") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java index 96380a82df6..e95858a1e41 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java @@ -71,35 +71,33 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams; -import org.apache.fineract.accounting.common.AccountingRuleType; +import org.apache.fineract.accounting.common.AccountingValidations; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartDataValidator; +import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.PreClosurePenalInterestOnType; +import org.apache.fineract.portfolio.savings.SavingsApiConstants; import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType; import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType; import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class DepositProductDataValidator { private final FromJsonHelper fromApiJsonHelper; private final InterestRateChartDataValidator chartDataValidator; - - @Autowired - public DepositProductDataValidator(FromJsonHelper fromApiJsonHelper, InterestRateChartDataValidator chartDataValidator) { - this.fromApiJsonHelper = fromApiJsonHelper; - this.chartDataValidator = chartDataValidator; - } + private final SavingsProductAccountingDataValidator savingsProductAccountingDataValidator; public void validateForFixedDepositCreate(final String json) { if (StringUtils.isBlank(json)) { @@ -115,7 +113,7 @@ public void validateForFixedDepositCreate(final String json) { 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 void validateForRecurringDepositCreate(final String json) { 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 void validateForRecurringDepositUpdate(final String json) { } 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 @@ private void validateDepositDetailForCreate(final JsonElement element, final Fro 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 @@ private void validateDepositDetailForCreate(final JsonElement element, final Fro final Integer accountingRuleType = fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault()); baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3); - if (isCashBasedAccounting(accountingRuleType)) { - - final Long savingsControlAccountId = fromApiJsonHelper - .extractLongNamed(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(); + Boolean isDormancyActive = this.fromApiJsonHelper.extractBooleanNamed(SavingsApiConstants.isDormancyTrackingActiveParamName, + element); + if (isDormancyActive == null) { + isDormancyActive = false; + } - validatePaymentChannelFundSourceMappings(fromApiJsonHelper, baseDataValidator, element); - validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element); + if (AccountingValidations.isCashBasedAccounting(accountingRuleType) + || AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) { + savingsProductAccountingDataValidator.evaluateAccountingDataForCreate(accountingRuleType, isDormancyActive, element, + baseDataValidator, accountType); } validateTaxWithHoldingParams(baseDataValidator, element, true); @@ -542,8 +509,8 @@ public void validateDepositDetailForUpdate(final JsonElement element, final From baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId) .ignoreIfNull().integerGreaterThanZero(); - validatePaymentChannelFundSourceMappings(fromApiJsonHelper, baseDataValidator, element); - validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element); + savingsProductAccountingDataValidator.validatePaymentChannelFundSourceMappings(baseDataValidator, element); + savingsProductAccountingDataValidator.validateChargeToIncomeAccountMappings(baseDataValidator, element); validateTaxWithHoldingParams(baseDataValidator, element, false); } @@ -611,76 +578,6 @@ private void throwExceptionIfValidationWarningsExist(final List 0) { - int i = 0; - do { - final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject(); - final Long paymentTypeId = jsonObject.get(SavingProductAccountingParams.PAYMENT_TYPE.getValue()).getAsLong(); - final Long paymentSpecificFundAccountId = jsonObject.get(SavingProductAccountingParams.FUND_SOURCE.getValue()) - .getAsLong(); - baseDataValidator.reset() - .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]." - + SavingProductAccountingParams.PAYMENT_TYPE.toString()) - .value(paymentTypeId).notNull().integerGreaterThanZero(); - baseDataValidator.reset() - .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]." - + SavingProductAccountingParams.FUND_SOURCE.getValue()) - .value(paymentSpecificFundAccountId).notNull().integerGreaterThanZero(); - i++; - } while (i < paymentChannelMappingArray.size()); - } - } - } - - private void validateChargeToIncomeAccountMappings(final FromJsonHelper fromApiJsonHelper, final DataValidatorBuilder baseDataValidator, - final JsonElement element) { - // validate for both fee and penalty charges - validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element, true); - validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element, true); - } - - private void validateChargeToIncomeAccountMappings(final FromJsonHelper fromApiJsonHelper, final DataValidatorBuilder baseDataValidator, - final JsonElement element, final boolean isPenalty) { - String parameterName; - if (isPenalty) { - parameterName = SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(); - } else { - parameterName = SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(); - } - - if (fromApiJsonHelper.parameterExists(parameterName, element)) { - final JsonArray chargeToIncomeAccountMappingArray = fromApiJsonHelper.extractJsonArrayNamed(parameterName, element); - if (chargeToIncomeAccountMappingArray != null && chargeToIncomeAccountMappingArray.size() > 0) { - int i = 0; - do { - final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject(); - final Long chargeId = fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.CHARGE_ID.getValue(), - jsonObject); - final Long incomeAccountId = fromApiJsonHelper - .extractLongNamed(SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), jsonObject); - baseDataValidator.reset().parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.CHARGE_ID.getValue()) - .value(chargeId).notNull().integerGreaterThanZero(); - baseDataValidator.reset() - .parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue()) - .value(incomeAccountId).notNull().integerGreaterThanZero(); - i++; - } while (i < chargeToIncomeAccountMappingArray.size()); - } - } - } - public void validateRecurringDetailForCreate(JsonElement element, DataValidatorBuilder baseDataValidator) { final Boolean isMandatoryDeposit = this.fromApiJsonHelper.extractBooleanNamed(isMandatoryDepositParamName, element); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java index 802e4c41d5b..92843e4c764 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java @@ -44,6 +44,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; @@ -56,15 +57,14 @@ import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.savings.SavingsApiConstants; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; import org.apache.fineract.portfolio.savings.domain.SavingsAccountSubStatusEnum; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; import org.apache.fineract.portfolio.savings.exception.TransactionBeforePivotDateNotAllowed; import org.apache.fineract.useradministration.domain.AppUser; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class SavingsAccountTransactionDataValidator { private final FromJsonHelper fromApiJsonHelper; @@ -72,15 +72,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) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductAccountingDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductAccountingDataValidator.java new file mode 100644 index 00000000000..233f4575331 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductAccountingDataValidator.java @@ -0,0 +1,184 @@ +/** + * 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 evaluateAccountingDataForCreate(final Integer accountingRuleType, final boolean isDormancyActive, final JsonElement element, + DataValidatorBuilder baseDataValidator, final DepositAccountType accountType) { + // GL Accounts for Cash or Accrual Periodic + if (AccountingValidations.isCashBasedAccounting(accountingRuleType) + || AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) { + + final Long savingsControlAccountId = fromApiJsonHelper + .extractLongNamed(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_CONTROL.getValue()).value(savingsControlAccountId) + .notNull().integerGreaterThanZero(); + + final Long savingsReferenceAccountId = fromApiJsonHelper + .extractLongNamed(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue()).value(savingsReferenceAccountId) + .notNull().integerGreaterThanZero(); + + final Long transfersInSuspenseAccountId = fromApiJsonHelper + .extractLongNamed(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue()) + .value(transfersInSuspenseAccountId).notNull().integerGreaterThanZero(); + + final Long interestOnSavingsAccountId = fromApiJsonHelper + .extractLongNamed(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue()) + .value(interestOnSavingsAccountId).notNull().integerGreaterThanZero(); + + final Long incomeFromFeeId = fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), + element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_FEES.getValue()).value(incomeFromFeeId).notNull() + .integerGreaterThanZero(); + + final Long incomeFromPenaltyId = fromApiJsonHelper + .extractLongNamed(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue()).value(incomeFromPenaltyId) + .notNull().integerGreaterThanZero(); + + if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) { + final Long overdraftControlAccountId = fromApiJsonHelper + .extractLongNamed(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue()) + .value(overdraftControlAccountId).notNull().integerGreaterThanZero(); + + final Long incomeFromInterest = fromApiJsonHelper + .extractLongNamed(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue()).value(incomeFromInterest) + .notNull().integerGreaterThanZero(); + + final Long writtenoff = fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), + element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue()).value(writtenoff).notNull() + .integerGreaterThanZero(); + } + + if (isDormancyActive) { + final Long escheatLiabilityAccountId = fromApiJsonHelper + .extractLongNamed(SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue()) + .value(escheatLiabilityAccountId).notNull().integerGreaterThanZero(); + } + } + // GL Accounts for Accrual Period only + if (AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) { + final Long feeReceivableAccountId = fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), + element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.FEES_RECEIVABLE.getValue()).value(feeReceivableAccountId) + .notNull().integerGreaterThanZero(); + + final Long penaltyReceivableAccountId = fromApiJsonHelper + .extractLongNamed(SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue()) + .value(penaltyReceivableAccountId).notNull().integerGreaterThanZero(); + + final Long interestPayableAccountId = fromApiJsonHelper + .extractLongNamed(SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), element); + baseDataValidator.reset().parameter(SavingProductAccountingParams.INTEREST_PAYABLE.getValue()).value(interestPayableAccountId) + .notNull().integerGreaterThanZero(); + } + + validatePaymentChannelFundSourceMappings(baseDataValidator, element); + validateChargeToIncomeAccountMappings(baseDataValidator, element); + } + + /** + * Validation for advanced accounting options + */ + public void validatePaymentChannelFundSourceMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) { + if (fromApiJsonHelper.parameterExists(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element)) { + final JsonArray paymentChannelMappingArray = fromApiJsonHelper + .extractJsonArrayNamed(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element); + if (paymentChannelMappingArray != null && paymentChannelMappingArray.size() > 0) { + int i = 0; + do { + final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject(); + final Long paymentTypeId = jsonObject.get(SavingProductAccountingParams.PAYMENT_TYPE.getValue()).getAsLong(); + final Long paymentSpecificFundAccountId = jsonObject.get(SavingProductAccountingParams.FUND_SOURCE.getValue()) + .getAsLong(); + baseDataValidator.reset() + .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]." + + SavingProductAccountingParams.PAYMENT_TYPE.toString()) + .value(paymentTypeId).notNull().integerGreaterThanZero(); + baseDataValidator.reset() + .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]." + + SavingProductAccountingParams.FUND_SOURCE.getValue()) + .value(paymentSpecificFundAccountId).notNull().integerGreaterThanZero(); + i++; + } while (i < paymentChannelMappingArray.size()); + } + } + } + + public void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) { + // validate for both fee and penalty charges + validateChargeToIncomeAccountMappings(baseDataValidator, element, true); + validateChargeToIncomeAccountMappings(baseDataValidator, element, true); + } + + private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element, + final boolean isPenalty) { + String parameterName; + if (isPenalty) { + parameterName = SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(); + } else { + parameterName = SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(); + } + + if (this.fromApiJsonHelper.parameterExists(parameterName, element)) { + final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element); + if (chargeToIncomeAccountMappingArray != null && chargeToIncomeAccountMappingArray.size() > 0) { + int i = 0; + do { + final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject(); + final Long chargeId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.CHARGE_ID.getValue(), + jsonObject); + final Long incomeAccountId = this.fromApiJsonHelper + .extractLongNamed(SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), jsonObject); + baseDataValidator.reset().parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.CHARGE_ID.getValue()) + .value(chargeId).notNull().integerGreaterThanZero(); + baseDataValidator.reset() + .parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue()) + .value(incomeAccountId).notNull().integerGreaterThanZero(); + i++; + } while (i < chargeToIncomeAccountMappingArray.size()); + } + } + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java index 368c1a87a97..c0045f4e66a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java @@ -50,9 +50,7 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; -import com.google.gson.JsonArray; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -64,28 +62,32 @@ 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 SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList( SavingsApiConstants.localeParamName, SavingsApiConstants.monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName, inMultiplesOfParamName, @@ -95,24 +97,20 @@ 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.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), - SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), - SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), isDormancyTrackingActiveParamName, daysToDormancyParamName, - daysToInactiveParamName, daysToEscheatParamName, allowOverdraftParamName, overdraftLimitParamName, - nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, - SavingsApiConstants.minRequiredBalanceParamName, SavingsApiConstants.enforceMinRequiredBalanceParamName, - SavingsApiConstants.maxAllowedLienLimitParamName, SavingsApiConstants.lienAllowedParamName, - minBalanceForInterestCalculationParamName, withHoldTaxParamName, taxGroupIdParamName)); - - @Autowired - public SavingsProductDataValidator(final FromJsonHelper fromApiJsonHelper) { - this.fromApiJsonHelper = fromApiJsonHelper; - } + SavingProductAccountingParams.INTEREST_PAYABLE.getValue(), SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), + SavingProductAccountingParams.FEES_RECEIVABLE.getValue(), SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), + SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(), + isDormancyTrackingActiveParamName, daysToDormancyParamName, daysToInactiveParamName, daysToEscheatParamName, + allowOverdraftParamName, overdraftLimitParamName, nominalAnnualInterestRateOverdraftParamName, + minOverdraftForInterestCalculationParamName, SavingsApiConstants.minRequiredBalanceParamName, + SavingsApiConstants.enforceMinRequiredBalanceParamName, SavingsApiConstants.maxAllowedLienLimitParamName, + SavingsApiConstants.lienAllowedParamName, minBalanceForInterestCalculationParamName, withHoldTaxParamName, + taxGroupIdParamName)); public void validateForCreate(final String json) { @@ -207,28 +205,6 @@ public void validateForCreate(final String json) { } } - /* - * 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,7 +232,10 @@ public void validateForCreate(final String json) { } // 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) { final Long daysToInact = this.fromApiJsonHelper.extractLongNamed(daysToInactiveParamName, element); @@ -278,62 +257,10 @@ public void validateForCreate(final String json) { 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.evaluateAccountingDataForCreate(accountingRuleType, isDormancyActive, element, + baseDataValidator, DepositAccountType.SAVINGS_DEPOSIT); } validateOverdraftParams(baseDataValidator, element); @@ -536,8 +463,8 @@ public void validateForUpdate(final String json, final SavingsProduct product) { } } - validatePaymentChannelFundSourceMappings(baseDataValidator, element); - validateChargeToIncomeAccountMappings(baseDataValidator, element); + savingsProductAccountingDataValidator.validatePaymentChannelFundSourceMappings(baseDataValidator, element); + savingsProductAccountingDataValidator.validateChargeToIncomeAccountMappings(baseDataValidator, element); validateOverdraftParams(baseDataValidator, element); if (this.fromApiJsonHelper.parameterExists(minBalanceForInterestCalculationParamName, element)) { @@ -558,74 +485,6 @@ private void throwExceptionIfValidationWarningsExist(final List 0) { - int i = 0; - do { - final JsonObject jsonObject = paymentChannelMappingArray.get(i).getAsJsonObject(); - final Long paymentTypeId = jsonObject.get(SavingProductAccountingParams.PAYMENT_TYPE.getValue()).getAsLong(); - final Long paymentSpecificFundAccountId = jsonObject.get(SavingProductAccountingParams.FUND_SOURCE.getValue()) - .getAsLong(); - baseDataValidator.reset() - .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]." - + SavingProductAccountingParams.PAYMENT_TYPE.toString()) - .value(paymentTypeId).notNull().integerGreaterThanZero(); - baseDataValidator.reset() - .parameter(SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue() + "[" + i + "]." - + SavingProductAccountingParams.FUND_SOURCE.getValue()) - .value(paymentSpecificFundAccountId).notNull().integerGreaterThanZero(); - i++; - } while (i < paymentChannelMappingArray.size()); - } - } - } - - private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) { - // validate for both fee and penalty charges - validateChargeToIncomeAccountMappings(baseDataValidator, element, true); - validateChargeToIncomeAccountMappings(baseDataValidator, element, true); - } - - private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element, - final boolean isPenalty) { - String parameterName; - if (isPenalty) { - parameterName = SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(); - } else { - parameterName = SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(); - } - - if (this.fromApiJsonHelper.parameterExists(parameterName, element)) { - final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element); - if (chargeToIncomeAccountMappingArray != null && chargeToIncomeAccountMappingArray.size() > 0) { - int i = 0; - do { - final JsonObject jsonObject = chargeToIncomeAccountMappingArray.get(i).getAsJsonObject(); - final Long chargeId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.CHARGE_ID.getValue(), - jsonObject); - final Long incomeAccountId = this.fromApiJsonHelper - .extractLongNamed(SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), jsonObject); - baseDataValidator.reset().parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.CHARGE_ID.getValue()) - .value(chargeId).notNull().integerGreaterThanZero(); - baseDataValidator.reset() - .parameter(parameterName + "[" + i + "]." + SavingProductAccountingParams.INCOME_ACCOUNT_ID.getValue()) - .value(incomeAccountId).notNull().integerGreaterThanZero(); - i++; - } while (i < chargeToIncomeAccountMappingArray.size()); - } - } - } - private void validateOverdraftParams(final DataValidatorBuilder baseDataValidator, final JsonElement element) { if (this.fromApiJsonHelper.parameterExists(allowOverdraftParamName, element)) { final Boolean allowOverdraft = this.fromApiJsonHelper.extractBooleanNamed(allowOverdraftParamName, element); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositProductWritePlatformServiceJpaRepositoryImpl.java index 798b20263c2..26df0d0f122 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositProductWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositProductWritePlatformServiceJpaRepositoryImpl.java @@ -28,6 +28,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -47,17 +49,15 @@ import org.apache.fineract.portfolio.savings.domain.RecurringDepositProductRepository; import org.apache.fineract.portfolio.savings.exception.RecurringDepositProductNotFoundException; import org.apache.fineract.portfolio.tax.domain.TaxGroup; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service +@RequiredArgsConstructor public class RecurringDepositProductWritePlatformServiceJpaRepositoryImpl implements RecurringDepositProductWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(RecurringDepositProductWritePlatformServiceJpaRepositoryImpl.class); private final PlatformSecurityContext context; private final RecurringDepositProductRepository recurringDepositProductRepository; private final DepositProductDataValidator fromApiJsonDataValidator; @@ -65,21 +65,6 @@ public class RecurringDepositProductWritePlatformServiceJpaRepositoryImpl implem private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService; private final InterestRateChartAssembler chartAssembler; - @Autowired - public RecurringDepositProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, - final RecurringDepositProductRepository recurringDepositProductRepository, - final DepositProductDataValidator fromApiJsonDataValidator, final DepositProductAssembler depositProductAssembler, - final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService, - final InterestRateChartAssembler chartAssembler) { - this.context = context; - this.recurringDepositProductRepository = recurringDepositProductRepository; - this.fromApiJsonDataValidator = fromApiJsonDataValidator; - this.depositProductAssembler = depositProductAssembler; - - this.accountMappingWritePlatformService = accountMappingWritePlatformService; - this.chartAssembler = chartAssembler; - } - @Transactional @Override public CommandProcessingResult create(final JsonCommand command) { @@ -206,6 +191,6 @@ private void handleDataIntegrityIssues(final JsonCommand command, final Throwabl } private void logAsErrorUnexpectedDataIntegrityException(final Exception dae) { - LOG.error("Error occured.", dae); + log.error("Error occured.", dae); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java index 56b429e403b..fef8689e3a2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductWritePlatformServiceJpaRepositoryImpl.java @@ -28,6 +28,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -48,17 +50,15 @@ import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository; import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException; import org.apache.fineract.portfolio.tax.domain.TaxGroup; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service +@RequiredArgsConstructor public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements SavingsProductWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(SavingsProductWritePlatformServiceJpaRepositoryImpl.class); private final PlatformSecurityContext context; private final SavingsProductRepository savingProductRepository; private final SavingsProductDataValidator fromApiJsonDataValidator; @@ -66,20 +66,6 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService; private final FineractEntityAccessUtil fineractEntityAccessUtil; - @Autowired - public SavingsProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, - final SavingsProductRepository savingProductRepository, final SavingsProductDataValidator fromApiJsonDataValidator, - final SavingsProductAssembler savingsProductAssembler, - final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService, - final FineractEntityAccessUtil fineractEntityAccessUtil) { - this.context = context; - this.savingProductRepository = savingProductRepository; - this.fromApiJsonDataValidator = fromApiJsonDataValidator; - this.savingsProductAssembler = savingsProductAssembler; - this.accountMappingWritePlatformService = accountMappingWritePlatformService; - this.fineractEntityAccessUtil = fineractEntityAccessUtil; - } - /* * Guaranteed to throw an exception no matter what the data integrity issue is. */ @@ -103,7 +89,7 @@ private void handleDataIntegrityIssues(final JsonCommand command, final Throwabl } private void logAsErrorUnexpectedDataIntegrityException(final Exception dae) { - LOG.error("Error occured.", dae); + log.error("Error occured.", dae); } @Transactional diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java index db4dfe687f1..9121085425b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java @@ -43,6 +43,8 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; +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; @@ -134,6 +136,7 @@ public void setup() { this.journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec); this.schedulerJobHelper = new SchedulerJobHelper(requestSpec); this.periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(requestSpec, responseSpec); + this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec); this.systemTimeZone = TimeZone.getTimeZone(Utils.TENANT_TIME_ZONE); } @@ -274,7 +277,6 @@ private Integer applyForLoanApplication(final Integer clientID, final Integer lo @Test public void checkAccountingWithSavingsFlow() { - this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec); final Account assetAccount = this.accountHelper.createAssetAccount(); final Account incomeAccount = this.accountHelper.createIncomeAccount(); @@ -366,10 +368,107 @@ public void checkAccountingWithSavingsFlow() { 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 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); @@ -474,6 +573,12 @@ public void testRecurringDepositAccountingFlow() { 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); @@ -530,6 +635,15 @@ public static Integer createSavingsProduct(final String minOpenningBalance, fina return SavingsProductHelper.createSavingsProduct(savingsProductJSON, 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); + } + private Integer createFixedDepositProduct(final String validFrom, final String validTo, Account... accounts) { LOG.info("------------------------------CREATING NEW FIXED DEPOSIT PRODUCT ---------------------------------------"); FixedDepositProductHelper fixedDepositProductHelper = new FixedDepositProductHelper(requestSpec, responseSpec); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java index ca2650a9466..73fab66e0d5 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java @@ -25,6 +25,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.fineract.client.models.GetRecurringDepositProductsProductIdResponse; +import org.apache.fineract.client.util.JSON; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.accounting.Account; import org.slf4j.Logger; @@ -36,6 +38,7 @@ 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 static ArrayList retrieveAllRecurringDepositProducts(final RequestSpecifi return response; } + public static GetRecurringDepositProductsProductIdResponse getRecurringDepositProductById(final RequestSpecification requestSpec, + final ResponseSpecification responseSpec, final Integer productId) { + LOG.info("-------------------- RETRIEVING RECURRING DEPOSIT PRODUCT BY ID --------------------------"); + final String GET_RD_PRODUCT_BY_ID_URL = RECURRING_DEPOSIT_PRODUCT_URL + "/" + productId + "?" + Utils.TENANT_IDENTIFIER; + final String response = Utils.performServerGet(requestSpec, responseSpec, GET_RD_PRODUCT_BY_ID_URL); + return GSON.fromJson(response, GetRecurringDepositProductsProductIdResponse.class); + } + public static HashMap retrieveRecurringDepositProductById(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String productId) { LOG.info("-------------------- RETRIEVING RECURRING DEPOSIT PRODUCT BY ID --------------------------"); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java index 4ce70847df1..ab847621f97 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java @@ -26,6 +26,8 @@ import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; +import org.apache.fineract.client.models.GetSavingsProductsProductIdResponse; +import org.apache.fineract.client.util.JSON; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.accounting.Account; import org.slf4j.Logger; @@ -35,6 +37,7 @@ public class SavingsProductHelper { private static final Logger LOG = LoggerFactory.getLogger(SavingsProductHelper.class); + private static final Gson GSON = new JSON().getGson(); 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; @@ -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_BASED = "3"; private String nameOfSavingsProduct = Utils.uniqueRandomStringGenerator("SAVINGS_PRODUCT_", 6); private String shortName = Utils.uniqueRandomStringGenerator("", 4); @@ -143,6 +147,9 @@ public String build() { if (this.accountingRule.equals(CASH_BASED)) { map.putAll(getAccountMappingForCashBased()); } + if (this.accountingRule.equals(ACCRUAL_BASED)) { + map.putAll(getAccountMappingForAccrualBased()); + } if (this.isDormancyTrackingActive) { map.put("isDormancyTrackingActive", Boolean.toString(this.isDormancyTrackingActive)); map.put("daysToInactive", this.daysToInactive); @@ -222,6 +229,12 @@ public SavingsProductHelper withAccountingRuleAsCashBased(final Account[] accoun return this; } + public SavingsProductHelper withAccountingRuleAsAccrualBased(final Account[] account_list) { + this.accountingRule = ACCRUAL_BASED; + this.accountList = account_list; + return this; + } + public SavingsProductHelper withMinRequiredBalance(final String minRequiredBalance) { this.minRequiredBalance = minRequiredBalance; return this; @@ -306,11 +319,52 @@ private Map getAccountMappingForCashBased() { return map; } + private Map getAccountMappingForAccrualBased() { + final Map 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("feeReceivableAccountId", ID); + map.put("penaltyReceivableAccountId", 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"); } + 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); + } + public static void verifySavingsProductCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer generatedProductID) { LOG.info("------------------------------CHECK CLIENT DETAILS------------------------------------\n");