From 3015747f3d0ddce9287035ccba6609f497865eaf Mon Sep 17 00:00:00 2001 From: pramod Date: Mon, 11 Apr 2016 14:44:59 +0530 Subject: [PATCH] FINERACT-33 : savings withhold tax changes --- .../ClientSavingsIntegrationTest.java | 119 +++++- .../integrationtests/FixedDepositTest.java | 305 +++++++++++++ .../RecurringDepositTest.java | 404 ++++++++++++++++++ .../common/TaxComponentHelper.java | 64 +++ .../common/TaxGroupHelper.java | 68 +++ .../common/accounting/JournalEntryHelper.java | 15 +- .../FixedDepositAccountStatusChecker.java | 6 + .../FixedDepositProductHelper.java | 17 + .../RecurringDepositAccountStatusChecker.java | 6 + .../RecurringDepositProductHelper.java | 15 + .../common/savings/SavingsAccountHelper.java | 16 +- .../common/savings/SavingsProductHelper.java | 25 +- .../data/SavingsTransactionDTO.java | 8 +- .../journalentry/data/TaxPaymentDTO.java | 47 ++ .../service/AccountingProcessorHelper.java | 223 +++++++--- ...ashBasedAccountingProcessorForSavings.java | 7 + .../service/CommandWrapperBuilder.java | 40 ++ .../charge/api/ChargesApiConstants.java | 1 + .../portfolio/charge/data/ChargeData.java | 60 +-- .../portfolio/charge/domain/Charge.java | 63 ++- ...initionCommandFromApiJsonDeserializer.java | 12 +- .../ChargeReadPlatformServiceImpl.java | 32 +- ...WritePlatformServiceJpaRepositoryImpl.java | 39 +- .../LoanChargeReadPlatformServiceImpl.java | 4 +- .../savings/DepositsApiConstants.java | 5 +- .../SavingsAccountTransactionType.java | 12 +- .../savings/SavingsApiConstants.java | 14 +- .../api/FixedDepositProductsApiResource.java | 12 +- .../RecurringDepositProductsApiResource.java | 11 +- .../api/SavingsAccountsApiResource.java | 16 +- .../api/SavingsProductsApiResource.java | 13 +- .../savings/data/DepositAccountData.java | 17 +- .../data/DepositAccountDataValidator.java | 14 +- .../savings/data/DepositProductData.java | 47 +- .../data/DepositProductDataValidator.java | 30 ++ .../savings/data/FixedDepositAccountData.java | 25 +- .../savings/data/FixedDepositProductData.java | 38 +- .../data/RecurringDepositAccountData.java | 28 +- .../data/RecurringDepositProductData.java | 38 +- .../savings/data/SavingsAccountData.java | 43 +- .../data/SavingsAccountDataValidator.java | 11 + .../data/SavingsAccountStatusEnumData.java | 5 +- .../data/SavingsAccountSummaryData.java | 4 +- .../SavingsAccountTransactionEnumData.java | 6 + .../savings/data/SavingsProductData.java | 52 ++- .../data/SavingsProductDataValidator.java | 43 +- .../domain/DepositAccountAssembler.java | 16 +- .../DepositAccountDomainServiceJpa.java | 49 ++- .../domain/DepositProductAssembler.java | 33 +- .../savings/domain/FixedDepositAccount.java | 23 +- .../savings/domain/FixedDepositProduct.java | 14 +- .../domain/RecurringDepositAccount.java | 19 +- .../domain/RecurringDepositProduct.java | 10 +- .../savings/domain/SavingsAccount.java | 144 ++++++- .../domain/SavingsAccountAssembler.java | 28 +- .../savings/domain/SavingsAccountSummary.java | 13 +- .../domain/SavingsAccountTransaction.java | 68 ++- ...vingsAccountTransactionSummaryWrapper.java | 15 +- .../SavingsAccountTransactionTaxDetails.java | 63 +++ .../savings/domain/SavingsProduct.java | 98 ++++- .../domain/SavingsProductAssembler.java | 36 +- ...thHoldTaxSavingsAccountCommandHandler.java | 46 ++ ...DepositAccountReadPlatformServiceImpl.java | 37 +- ...DepositProductReadPlatformServiceImpl.java | 18 +- ...WritePlatformServiceJpaRepositoryImpl.java | 21 + ...WritePlatformServiceJpaRepositoryImpl.java | 22 + ...sAccountChargeReadPlatformServiceImpl.java | 5 +- ...SavingsAccountReadPlatformServiceImpl.java | 58 ++- .../SavingsAccountWritePlatformService.java | 2 + ...WritePlatformServiceJpaRepositoryImpl.java | 143 ++++--- .../savings/service/SavingsEnumerations.java | 29 +- ...SavingsProductReadPlatformServiceImpl.java | 98 +++-- ...WritePlatformServiceJpaRepositoryImpl.java | 33 +- .../portfolio/tax/api/TaxApiConstants.java | 38 ++ .../tax/api/TaxComponentApiResource.java | 140 ++++++ .../tax/api/TaxGroupApiResource.java | 142 ++++++ .../portfolio/tax/data/TaxComponentData.java | 112 +++++ .../tax/data/TaxComponentHistoryData.java | 39 ++ .../portfolio/tax/data/TaxGroupData.java | 63 +++ .../tax/data/TaxGroupMappingsData.java | 40 ++ .../portfolio/tax/domain/TaxComponent.java | 222 ++++++++++ .../tax/domain/TaxComponentHistory.java | 92 ++++ .../tax/domain/TaxComponentRepository.java | 26 ++ .../domain/TaxComponentRepositoryWrapper.java | 43 ++ .../portfolio/tax/domain/TaxGroup.java | 117 +++++ .../tax/domain/TaxGroupMappings.java | 120 ++++++ .../tax/domain/TaxGroupRepository.java | 26 ++ .../tax/domain/TaxGroupRepositoryWrapper.java | 43 ++ .../TaxComponentNotFoundException.java | 28 ++ .../exception/TaxGroupNotFoundException.java | 28 ++ .../TaxMappingNotFoundException.java | 34 ++ .../CreateTaxComponentCommandHandler.java | 45 ++ .../handler/CreateTaxGroupCommandHandler.java | 45 ++ .../UpdateTaxComponentCommandHandler.java | 45 ++ .../handler/UpdateTaxGroupCommandHandler.java | 45 ++ .../tax/serialization/TaxValidator.java | 365 ++++++++++++++++ .../portfolio/tax/service/TaxAssembler.java | 163 +++++++ .../tax/service/TaxReadPlatformService.java | 44 ++ .../service/TaxReadPlatformServiceImpl.java | 305 +++++++++++++ .../portfolio/tax/service/TaxUtils.java | 94 ++++ .../tax/service/TaxWritePlatformService.java | 34 ++ .../service/TaxWritePlatformServiceImpl.java | 111 +++++ .../core_db/V298__savings_interest_tax.sql | 111 +++++ 103 files changed, 5490 insertions(+), 491 deletions(-) create mode 100644 fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/TaxComponentHelper.java create mode 100644 fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/TaxGroupHelper.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/TaxPaymentDTO.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionTaxDetails.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateWithHoldTaxSavingsAccountCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxApiConstants.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxComponentApiResource.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxGroupApiResource.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentHistoryData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupMappingsData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponent.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentHistory.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentRepository.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentRepositoryWrapper.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroup.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupMappings.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupRepository.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupRepositoryWrapper.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxComponentNotFoundException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxGroupNotFoundException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxMappingNotFoundException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/CreateTaxComponentCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/CreateTaxGroupCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/UpdateTaxComponentCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/UpdateTaxGroupCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/serialization/TaxValidator.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxAssembler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxUtils.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformServiceImpl.java create mode 100644 fineract-provider/src/main/resources/sql/migrations/core_db/V298__savings_interest_tax.sql diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java index 515eb5a62..48a186b6a 100755 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java @@ -26,6 +26,7 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.List; @@ -33,6 +34,8 @@ import java.util.Locale; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.CommonConstants; +import org.apache.fineract.integrationtests.common.TaxComponentHelper; +import org.apache.fineract.integrationtests.common.TaxGroupHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.charges.ChargesHelper; import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper; @@ -789,7 +792,106 @@ public class ClientSavingsIntegrationTest { assertEquals("validation.msg.savingsaccount.close.results.in.balance.not.zero", savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); } + + @Test + public void testSavingsAccount_WITH_WITHHOLD_TAX() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assert.assertNotNull(clientID); + final String minBalanceForInterestCalculation = null; + final String minRequiredBalance = null; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = false; + final String percentage = "10"; + final Integer taxGroupId = createTaxGroup(percentage); + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, + minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft, String.valueOf(taxGroupId)); + Assert.assertNotNull(savingsProductID); + + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertNotNull(savingsProductID); + + HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId, + ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertTrue(modifications.containsKey("submittedOnDate")); + + HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId); + SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + + final HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId); + this.savingsAccountHelper.calculateInterestForSavings(savingsId); + HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + assertEquals(summaryBefore, summary); + + this.savingsAccountHelper.postInterestForSavings(savingsId); + summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + Assert.assertFalse(summaryBefore.equals(summary)); + Assert.assertNotNull(summary.get("totalWithholdTax")); + Float expected = (Float) summary.get("totalDeposits") + (Float) summary.get("totalInterestPosted") + - (Float) summary.get("totalWithholdTax"); + Float actual = (Float) summary.get("accountBalance"); + Assert.assertEquals(expected, actual, 1); + + } + + @Test + public void testSavingsAccount_WITH_WITHHOLD_TAX_DISABLE_AT_ACCOUNT_LEVEL() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assert.assertNotNull(clientID); + final String minBalanceForInterestCalculation = null; + final String minRequiredBalance = null; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = false; + final String percentage = "10"; + final Integer taxGroupId = createTaxGroup(percentage); + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, + minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft, String.valueOf(taxGroupId)); + Assert.assertNotNull(savingsProductID); + + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertNotNull(savingsProductID); + + HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId, + ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertTrue(modifications.containsKey("submittedOnDate")); + + HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId); + SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + + final HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId); + this.savingsAccountHelper.calculateInterestForSavings(savingsId); + HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + assertEquals(summaryBefore, summary); + + final HashMap changes = this.savingsAccountHelper.updateSavingsAccountWithHoldTaxStatus(savingsId, false); + Assert.assertTrue(changes.containsKey("withHoldTax")); + + this.savingsAccountHelper.postInterestForSavings(savingsId); + summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + Assert.assertFalse(summaryBefore.equals(summary)); + Assert.assertNull(summary.get("totalWithholdTax")); + Float expected = (Float) summary.get("totalDeposits") + (Float) summary.get("totalInterestPosted"); + Float actual = (Float) summary.get("accountBalance"); + Assert.assertEquals(expected, actual, 1); + + } + + private HashMap activateSavingsAccount(final Integer savingsId, final String activationDate) { final HashMap status = this.savingsAccountHelper.activateSavingsAccount(savingsId, activationDate); return status; @@ -798,6 +900,14 @@ public class ClientSavingsIntegrationTest { private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String minOpenningBalance, String minBalanceForInterestCalculation, String minRequiredBalance, String enforceMinRequiredBalance, final boolean allowOverdraft) { + final String taxGroupId = null; + return createSavingsProduct(requestSpec, responseSpec, minOpenningBalance, minBalanceForInterestCalculation, minRequiredBalance, + enforceMinRequiredBalance, allowOverdraft, taxGroupId); + } + + private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + final String minOpenningBalance, String minBalanceForInterestCalculation, String minRequiredBalance, + String enforceMinRequiredBalance, final boolean allowOverdraft, final String taxGroupId) { System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------"); SavingsProductHelper savingsProductHelper = new SavingsProductHelper(); if (allowOverdraft) { @@ -816,9 +926,16 @@ public class ClientSavingsIntegrationTest { .withMinBalanceForInterestCalculation(minBalanceForInterestCalculation) // .withMinRequiredBalance(minRequiredBalance).withEnforceMinRequiredBalance(enforceMinRequiredBalance) - .withMinimumOpenningBalance(minOpenningBalance).build(); + .withMinimumOpenningBalance(minOpenningBalance).withWithHoldTax(taxGroupId).build(); return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec); } + + private Integer createTaxGroup(final String percentage){ + final Integer liabilityAccountId = null; + final Integer taxComponentId = TaxComponentHelper.createTaxComponent(this.requestSpec, this.responseSpec, percentage, liabilityAccountId); + return TaxGroupHelper.createTaxGroup(this.requestSpec, this.responseSpec, Arrays.asList(taxComponentId)); + } + /* * private void verifySavingsInterest(final Object savingsInterest) { diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FixedDepositTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FixedDepositTest.java index fa638bc34..d8a0ef6fa 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FixedDepositTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/FixedDepositTest.java @@ -27,6 +27,7 @@ import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; @@ -36,6 +37,9 @@ import java.util.Locale; import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.CommonConstants; +import org.apache.fineract.integrationtests.common.SchedulerJobHelper; +import org.apache.fineract.integrationtests.common.TaxComponentHelper; +import org.apache.fineract.integrationtests.common.TaxGroupHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.accounting.Account; import org.apache.fineract.integrationtests.common.accounting.AccountHelper; @@ -240,6 +244,7 @@ public class FixedDepositTest { this.responseSpec, fixedDepositAccountId.toString()); FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsPrematureClosed(fixedDepositAccountStatusHashMap); + /*** * Verify journal entry transactions for preclosure transaction */ @@ -253,6 +258,284 @@ public class FixedDepositTest { } + + @Test + public void testFixedDepositAccountWithPrematureClosureTypeWithdrawal_WITH_HOLD_TAX() { + this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec); + this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec); + + /*** + * Create GL Accounts for product account mapping + */ + 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 Account liabilityAccountForTax = this.accountHelper.createLiabilityAccount(); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US); + DateFormat currentDateFormat = new SimpleDateFormat("dd"); + + Calendar todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -3); + final String VALID_FROM = dateFormat.format(todaysDate.getTime()); + todaysDate.add(Calendar.YEAR, 10); + final String VALID_TO = dateFormat.format(todaysDate.getTime()); + + todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -1); + final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime()); + final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime()); + + Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime())); + Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE); + Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1; + todaysDate.add(Calendar.DATE, numberOfDaysLeft); + final String INTEREST_POSTED_DATE = dateFormat.format(todaysDate.getTime()); + final String CLOSED_ON_DATE = dateFormat.format(Calendar.getInstance().getTime()); + + Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assert.assertNotNull(clientId); + + /*** + * Create FD product with CashBased accounting enabled + */ + final String accountingRule = CASH_BASED; + final Integer taxGroupId = createTaxGroup("10", liabilityAccountForTax); + Integer fixedDepositProductId = createFixedDepositProductWithWithHoldTax(VALID_FROM, VALID_TO, String.valueOf(taxGroupId), + accountingRule, assetAccount, liabilityAccount, incomeAccount, expenseAccount); + Assert.assertNotNull(fixedDepositProductId); + + /*** + * Apply for FD account with created product and verify status + */ + Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), + SUBMITTED_ON_DATE, WHOLE_TERM); + Assert.assertNotNull(fixedDepositAccountId); + + HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec, + this.responseSpec, fixedDepositAccountId.toString()); + FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap); + + /*** + * Approve the FD account and verify whether account is approved + */ + fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE); + FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap); + + /*** + * Activate the FD Account and verify whether account is activated + */ + fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE); + FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap); + + HashMap accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId); + + Float depositAmount = (Float) accountSummary.get("totalDeposits"); + + /*** + * Verify journal entries posted for initial deposit transaction which + * happened at activation time + */ + final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.DEBIT) }; + final JournalEntry[] liablilityAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.CREDIT) }; + this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, ACTIVATION_DATE, assetAccountInitialEntry); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, ACTIVATION_DATE, liablilityAccountInitialEntry); + + /*** + * Update interest earned of FD account + */ + fixedDepositAccountId = this.fixedDepositAccountHelper.calculateInterestForFixedDeposit(fixedDepositAccountId); + Assert.assertNotNull(fixedDepositAccountId); + + /*** + * Post interest and verify the account summary + */ + Integer transactionIdForPostInterest = this.fixedDepositAccountHelper.postInterestForFixedDeposit(fixedDepositAccountId); + Assert.assertNotNull(transactionIdForPostInterest); + + accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId); + Float totalInterestPosted = (Float) accountSummary.get("totalInterestPosted"); + Assert.assertNull(accountSummary.get("totalWithholdTax")); + + /*** + * Verify journal entries transactions for interest posting transaction + */ + final JournalEntry[] expenseAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.DEBIT) }; + final JournalEntry[] liablilityAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.CREDIT) }; + this.journalEntryHelper.checkJournalEntryForAssetAccount(expenseAccount, INTEREST_POSTED_DATE, expenseAccountEntry); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, INTEREST_POSTED_DATE, liablilityAccountEntry); + + /*** + * Preclose the FD account verify whether account is preClosed + */ + HashMap fixedDepositPrematureData = this.fixedDepositAccountHelper.calculatePrematureAmountForFixedDeposit(fixedDepositAccountId, + CLOSED_ON_DATE); + + Integer prematureClosureTransactionId = (Integer) this.fixedDepositAccountHelper.prematureCloseForFixedDeposit( + fixedDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_WITHDRAW_DEPOSIT, null, CommonConstants.RESPONSE_RESOURCE_ID); + Assert.assertNotNull(prematureClosureTransactionId); + + fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec, + this.responseSpec, fixedDepositAccountId.toString()); + FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsPrematureClosed(fixedDepositAccountStatusHashMap); + + /*** + * Verify journal entry transactions for preclosure transaction + */ + HashMap accountDetails = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec, + fixedDepositAccountId); + Float maturityAmount = Float.valueOf(accountDetails.get("maturityAmount").toString()); + + HashMap summary = (HashMap) accountDetails.get("summary"); + Assert.assertNotNull(summary.get("totalWithholdTax")); + Float withHoldTax = (Float) summary.get("totalWithholdTax"); + + this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, CLOSED_ON_DATE, new JournalEntry(maturityAmount, + JournalEntry.TransactionType.CREDIT)); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, CLOSED_ON_DATE, new JournalEntry(maturityAmount, + JournalEntry.TransactionType.DEBIT)); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccountForTax, CLOSED_ON_DATE, new JournalEntry(withHoldTax, + JournalEntry.TransactionType.CREDIT)); + + } + + + @Test + public void testFixedDepositAccountClosureTypeWithdrawal_WITH_HOLD_TAX() throws InterruptedException { + this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec); + this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec); + + /*** + * Create GL Accounts for product account mapping + */ + 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 Account liabilityAccountForTax = this.accountHelper.createLiabilityAccount(); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US); + DateFormat currentDateFormat = new SimpleDateFormat("dd"); + + Calendar todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -20); + final String VALID_FROM = dateFormat.format(todaysDate.getTime()); + todaysDate.add(Calendar.YEAR, 10); + final String VALID_TO = dateFormat.format(todaysDate.getTime()); + + todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -20); + final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime()); + final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime()); + + Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime())); + Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE); + Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1; + todaysDate.add(Calendar.DATE, numberOfDaysLeft); + Calendar closedOn = Calendar.getInstance(); + closedOn.add(Calendar.MONTH, -6); + final String CLOSED_ON_DATE = dateFormat.format(closedOn.getTime()); + + Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assert.assertNotNull(clientId); + + /*** + * Create FD product with CashBased accounting enabled + */ + final String accountingRule = CASH_BASED; + final Integer taxGroupId = createTaxGroup("10", liabilityAccountForTax); + Integer fixedDepositProductId = createFixedDepositProductWithWithHoldTax(VALID_FROM, VALID_TO, String.valueOf(taxGroupId), + accountingRule, assetAccount, liabilityAccount, incomeAccount, expenseAccount); + Assert.assertNotNull(fixedDepositProductId); + + /*** + * Apply for FD account with created product and verify status + */ + Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), + SUBMITTED_ON_DATE, WHOLE_TERM); + Assert.assertNotNull(fixedDepositAccountId); + + HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec, + this.responseSpec, fixedDepositAccountId.toString()); + FixedDepositAccountStatusChecker.verifyFixedDepositIsPending(fixedDepositAccountStatusHashMap); + + /*** + * Approve the FD account and verify whether account is approved + */ + fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE); + FixedDepositAccountStatusChecker.verifyFixedDepositIsApproved(fixedDepositAccountStatusHashMap); + + /*** + * Activate the FD Account and verify whether account is activated + */ + fixedDepositAccountStatusHashMap = this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, ACTIVATION_DATE); + FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap); + + HashMap accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId); + + Float depositAmount = (Float) accountSummary.get("totalDeposits"); + + /*** + * Verify journal entries posted for initial deposit transaction which + * happened at activation time + */ + final JournalEntry[] assetAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.DEBIT) }; + final JournalEntry[] liablilityAccountInitialEntry = { new JournalEntry(depositAmount, JournalEntry.TransactionType.CREDIT) }; + this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, ACTIVATION_DATE, assetAccountInitialEntry); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, ACTIVATION_DATE, liablilityAccountInitialEntry); + + /*** + * Update interest earned of FD account + */ + fixedDepositAccountId = this.fixedDepositAccountHelper.calculateInterestForFixedDeposit(fixedDepositAccountId); + Assert.assertNotNull(fixedDepositAccountId); + + /*** + * Post interest and verify the account summary + */ + Integer transactionIdForPostInterest = this.fixedDepositAccountHelper.postInterestForFixedDeposit(fixedDepositAccountId); + Assert.assertNotNull(transactionIdForPostInterest); + + accountSummary = this.fixedDepositAccountHelper.getFixedDepositSummary(fixedDepositAccountId); + Float totalInterestPosted = (Float) accountSummary.get("totalInterestPosted"); + Assert.assertNull(accountSummary.get("totalWithholdTax")); + + /*** + * FD account verify whether account is matured + */ + + SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec, responseSpec); + String JobName = "Update Deposit Accounts Maturity details"; + schedulerJobHelper.executeJob(JobName); + + HashMap accountDetails = this.fixedDepositAccountHelper.getFixedDepositAccountById(this.requestSpec, this.responseSpec, + fixedDepositAccountId); + + HashMap summary = (HashMap) accountDetails.get("summary"); + Assert.assertNotNull(summary.get("totalWithholdTax")); + Float withHoldTax = (Float) summary.get("totalWithholdTax"); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccountForTax, CLOSED_ON_DATE, new JournalEntry(withHoldTax, + JournalEntry.TransactionType.CREDIT)); + + + fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec, + this.responseSpec, fixedDepositAccountId.toString()); + FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsMatured(fixedDepositAccountStatusHashMap); + + } + + @Test public void testFixedDepositAccountWithPeriodInterestRateChart() { final String chartToUse = "period"; @@ -2139,6 +2422,22 @@ public class FixedDepositTest { return FixedDepositProductHelper.createFixedDepositProduct(fixedDepositProductJSON, requestSpec, responseSpec); } + + private Integer createFixedDepositProductWithWithHoldTax(final String validFrom, final String validTo, final String taxGroupId, + final String accountingRule, Account... accounts) { + System.out.println("------------------------------CREATING NEW FIXED DEPOSIT PRODUCT ---------------------------------------"); + FixedDepositProductHelper fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec); + if (accountingRule.equals(CASH_BASED)) { + fixedDepositProductHelper = fixedDepositProductHelper.withAccountingRuleAsCashBased(accounts); + } else if (accountingRule.equals(NONE)) { + fixedDepositProductHelper = fixedDepositProductHelper.withAccountingRuleAsNone(); + } + final String fixedDepositProductJSON = fixedDepositProductHelper.withPeriodRangeChart() // + .withWithHoldTax(taxGroupId)// + .build(validFrom, validTo); + return FixedDepositProductHelper.createFixedDepositProduct(fixedDepositProductJSON, requestSpec, responseSpec); + } + private Integer createFixedDepositProduct(final String validFrom, final String validTo, final String accountingRule, final String chartToBePicked, Account... accounts) { System.out.println("------------------------------CREATING NEW FIXED DEPOSIT PRODUCT ---------------------------------------"); @@ -2262,6 +2561,12 @@ public class FixedDepositTest { Assert.assertEquals(financialActivityId, ((HashMap) mappingDetails.get("financialActivityData")).get("id")); Assert.assertEquals(glAccount.getAccountID(), ((HashMap) mappingDetails.get("glAccountData")).get("id")); } + + private Integer createTaxGroup(final String percentage, final Account liabilityAccountForTax){ + final Integer liabilityAccountId = liabilityAccountForTax.getAccountID(); + final Integer taxComponentId = TaxComponentHelper.createTaxComponent(this.requestSpec, this.responseSpec, percentage, liabilityAccountId); + return TaxGroupHelper.createTaxGroup(this.requestSpec, this.responseSpec, Arrays.asList(taxComponentId)); + } /** * Delete the Liability transfer account diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/RecurringDepositTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/RecurringDepositTest.java index 1d52aec36..5ae093e02 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/RecurringDepositTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/RecurringDepositTest.java @@ -25,6 +25,7 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.List; @@ -35,6 +36,9 @@ import net.sf.ehcache.transaction.xa.EhcacheXAException; import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.CommonConstants; +import org.apache.fineract.integrationtests.common.SchedulerJobHelper; +import org.apache.fineract.integrationtests.common.TaxComponentHelper; +import org.apache.fineract.integrationtests.common.TaxGroupHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.accounting.Account; import org.apache.fineract.integrationtests.common.accounting.AccountHelper; @@ -472,6 +476,384 @@ public class RecurringDepositTest { } + @Test + public void testRecurringDepositAccountWithPrematureClosureTypeTransferToSavings_WITH_HOLD_TAX() { + this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec); + this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec); + + + /*** + * Create GL Accounts for product account mapping + */ + 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 Account liabilityAccountForTax = this.accountHelper.createLiabilityAccount(); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US); + DateFormat currentDateFormat = new SimpleDateFormat("dd"); + + Calendar todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -3); + final String VALID_FROM = dateFormat.format(todaysDate.getTime()); + todaysDate.add(Calendar.YEAR, 10); + final String VALID_TO = dateFormat.format(todaysDate.getTime()); + + todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -1); + final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime()); + final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime()); + + Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime())); + Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE); + Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1; + todaysDate.add(Calendar.DATE, numberOfDaysLeft); + final String INTEREST_POSTED_DATE = dateFormat.format(todaysDate.getTime()); + final String CLOSED_ON_DATE = dateFormat.format(Calendar.getInstance().getTime()); + + /*** + * Create client for applying Deposit and Savings accounts + */ + Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assert.assertNotNull(clientId); + + /*** + * Create Savings product with CashBased accounting enabled + */ + final String accountingRule = CASH_BASED; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, accountingRule, + assetAccount, liabilityAccount, incomeAccount, expenseAccount); + Assert.assertNotNull(savingsProductID); + + /*** + * Create Savings account and verify status is pending + */ + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientId, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertNotNull(savingsProductID); + + HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId); + SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); + + /*** + * Approve the savings account and verify account is approved + */ + savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + /*** + * Activate the savings account and verify account is activated + */ + savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + + /*** + * Create RD product with CashBased accounting enabled + */ + final Integer taxGroupId = createTaxGroup("10", liabilityAccountForTax); + Integer recurringDepositProductId = createRecurringDepositProductWithWithHoldTax(VALID_FROM, VALID_TO,String.valueOf(taxGroupId), accountingRule, assetAccount, + liabilityAccount, incomeAccount, expenseAccount); + Assert.assertNotNull(recurringDepositProductId); + + /*** + * Apply for RD account with created product and verify status + */ + Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(), + VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE); + Assert.assertNotNull(recurringDepositAccountId); + + HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount( + this.requestSpec, this.responseSpec, recurringDepositAccountId.toString()); + RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap); + + /*** + * Approve the RD account and verify whether account is approved + */ + recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId, + APPROVED_ON_DATE); + RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap); + + /*** + * Activate the RD Account and verify whether account is activated + */ + recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId, + ACTIVATION_DATE); + RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap); + + HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec, + this.responseSpec, recurringDepositAccountId); + Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount"); + + /*** + * Perform Deposit transaction and verify journal entries are posted for + * the transaction + */ + Integer depositTransactionId = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId, + depositAmount, EXPECTED_FIRST_DEPOSIT_ON_DATE); + Assert.assertNotNull(depositTransactionId); + + this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, EXPECTED_FIRST_DEPOSIT_ON_DATE, new JournalEntry( + depositAmount, JournalEntry.TransactionType.DEBIT)); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, EXPECTED_FIRST_DEPOSIT_ON_DATE, new JournalEntry( + depositAmount, JournalEntry.TransactionType.CREDIT)); + + /*** + * Update interest earned field for RD account + */ + recurringDepositAccountId = this.recurringDepositAccountHelper.calculateInterestForRecurringDeposit(recurringDepositAccountId); + Assert.assertNotNull(recurringDepositAccountId); + + /*** + * Post interest and verify journal entries + */ + Integer transactionIdForPostInterest = this.recurringDepositAccountHelper + .postInterestForRecurringDeposit(recurringDepositAccountId); + Assert.assertNotNull(transactionIdForPostInterest); + + + HashMap accountSummary = this.recurringDepositAccountHelper.getRecurringDepositSummary(recurringDepositAccountId); + Float totalInterestPosted = (Float) accountSummary.get("totalInterestPosted"); + Assert.assertNull(accountSummary.get("totalWithholdTax")); + + + final JournalEntry[] expenseAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.DEBIT) }; + final JournalEntry[] liablilityAccountEntry = { new JournalEntry(totalInterestPosted, JournalEntry.TransactionType.CREDIT) }; + this.journalEntryHelper.checkJournalEntryForAssetAccount(expenseAccount, INTEREST_POSTED_DATE, expenseAccountEntry); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, INTEREST_POSTED_DATE, liablilityAccountEntry); + + /*** + * Get saving account balance before preClosing RD account + */ + HashMap savingsSummaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId); + Float balanceBefore = (Float) savingsSummaryBefore.get("accountBalance"); + + HashMap recurringDepositPrematureData = this.recurringDepositAccountHelper.calculatePrematureAmountForRecurringDeposit( + recurringDepositAccountId, CLOSED_ON_DATE); + + /*** + * Retrieve mapped financial account for liability transfer + */ + Account financialAccount = getMappedLiabilityFinancialAccount(); + + /*** + * Preclose the RD account verify whether account is preClosed + */ + Integer prematureClosureTransactionId = (Integer) this.recurringDepositAccountHelper.prematureCloseForRecurringDeposit( + recurringDepositAccountId, CLOSED_ON_DATE, CLOSURE_TYPE_TRANSFER_TO_SAVINGS, savingsId, + CommonConstants.RESPONSE_RESOURCE_ID); + Assert.assertNotNull(prematureClosureTransactionId); + + recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(this.requestSpec, + this.responseSpec, recurringDepositAccountId.toString()); + RecurringDepositAccountStatusChecker.verifyRecurringDepositAccountIsPrematureClosed(recurringDepositAccountStatusHashMap); + + recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec, + this.responseSpec, recurringDepositAccountId); + Float maturityAmount = Float.valueOf(recurringDepositAccountData.get("maturityAmount").toString()); + /*** + * Verify journal entry transactions for preclosure transaction As this + * transaction is an account transfer you should get financial account + * mapping details and verify amounts + */ + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, CLOSED_ON_DATE, new JournalEntry(maturityAmount, + JournalEntry.TransactionType.CREDIT), new JournalEntry(maturityAmount, JournalEntry.TransactionType.DEBIT)); + + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(financialAccount, CLOSED_ON_DATE, new JournalEntry(maturityAmount, + JournalEntry.TransactionType.DEBIT), new JournalEntry(maturityAmount, JournalEntry.TransactionType.CREDIT)); + /*** + * Verify rd account maturity amount and savings account balance + */ + HashMap recurringDepositData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec, + this.responseSpec, recurringDepositAccountId); + Float prematurityAmount = (Float) recurringDepositData.get("maturityAmount"); + HashMap summary = (HashMap) recurringDepositData.get("summary"); + Assert.assertNotNull(summary.get("totalWithholdTax")); + Float withHoldTax = (Float) summary.get("totalWithholdTax"); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccountForTax, CLOSED_ON_DATE, new JournalEntry(withHoldTax, + JournalEntry.TransactionType.CREDIT)); + + HashMap savingsSummaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId); + Float balanceAfter = (Float) savingsSummaryAfter.get("accountBalance"); + Float expectedSavingsBalance = balanceBefore + prematurityAmount; + + DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US)); + decimalFormat.applyPattern("."); + + Assert.assertEquals("Verifying Savings Account Balance after Premature Closure", decimalFormat.format(expectedSavingsBalance), + decimalFormat.format(balanceAfter)); + + } + + + + @Test + public void testRecurringDepositAccountWithClosureTypeTransferToSavings_WITH_HOLD_TAX() throws InterruptedException { + this.recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec); + this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + this.recurringDepositAccountHelper = new RecurringDepositAccountHelper(this.requestSpec, this.responseSpec); + + + /*** + * Create GL Accounts for product account mapping + */ + 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 Account liabilityAccountForTax = this.accountHelper.createLiabilityAccount(); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US); + DateFormat currentDateFormat = new SimpleDateFormat("dd"); + + Calendar todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -20); + final String VALID_FROM = dateFormat.format(todaysDate.getTime()); + todaysDate.add(Calendar.YEAR, 10); + final String VALID_TO = dateFormat.format(todaysDate.getTime()); + + todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -20); + final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime()); + final String EXPECTED_FIRST_DEPOSIT_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String MONTH_DAY = monthDayFormat.format(todaysDate.getTime()); + + Integer currentDate = new Integer(currentDateFormat.format(todaysDate.getTime())); + Integer daysInMonth = todaysDate.getActualMaximum(Calendar.DATE); + Integer numberOfDaysLeft = (daysInMonth - currentDate) + 1; + todaysDate.add(Calendar.DATE, numberOfDaysLeft); + final String INTEREST_POSTED_DATE = dateFormat.format(todaysDate.getTime()); + Calendar closedOn = Calendar.getInstance(); + closedOn.add(Calendar.MONTH, -6); + final String CLOSED_ON_DATE = dateFormat.format(closedOn.getTime()); + + /*** + * Create client for applying Deposit and Savings accounts + */ + Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assert.assertNotNull(clientId); + + /*** + * Create Savings product with CashBased accounting enabled + */ + final String accountingRule = CASH_BASED; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, accountingRule, + assetAccount, liabilityAccount, incomeAccount, expenseAccount); + Assert.assertNotNull(savingsProductID); + + /*** + * Create Savings account and verify status is pending + */ + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientId, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertNotNull(savingsProductID); + + HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId); + SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); + + /*** + * Approve the savings account and verify account is approved + */ + savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + /*** + * Activate the savings account and verify account is activated + */ + savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + + /*** + * Create RD product with CashBased accounting enabled + */ + final Integer taxGroupId = createTaxGroup("10", liabilityAccountForTax); + Integer recurringDepositProductId = createRecurringDepositProductWithWithHoldTax(VALID_FROM, VALID_TO,String.valueOf(taxGroupId), accountingRule, assetAccount, + liabilityAccount, incomeAccount, expenseAccount); + Assert.assertNotNull(recurringDepositProductId); + + /*** + * Apply for RD account with created product and verify status + */ + Integer recurringDepositAccountId = applyForRecurringDepositApplication(clientId.toString(), recurringDepositProductId.toString(), + VALID_FROM, VALID_TO, SUBMITTED_ON_DATE, WHOLE_TERM, EXPECTED_FIRST_DEPOSIT_ON_DATE); + Assert.assertNotNull(recurringDepositAccountId); + + HashMap recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount( + this.requestSpec, this.responseSpec, recurringDepositAccountId.toString()); + RecurringDepositAccountStatusChecker.verifyRecurringDepositIsPending(recurringDepositAccountStatusHashMap); + + /*** + * Approve the RD account and verify whether account is approved + */ + recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.approveRecurringDeposit(recurringDepositAccountId, + APPROVED_ON_DATE); + RecurringDepositAccountStatusChecker.verifyRecurringDepositIsApproved(recurringDepositAccountStatusHashMap); + + /*** + * Activate the RD Account and verify whether account is activated + */ + recurringDepositAccountStatusHashMap = this.recurringDepositAccountHelper.activateRecurringDeposit(recurringDepositAccountId, + ACTIVATION_DATE); + RecurringDepositAccountStatusChecker.verifyRecurringDepositIsActive(recurringDepositAccountStatusHashMap); + + HashMap recurringDepositAccountData = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec, + this.responseSpec, recurringDepositAccountId); + Float depositAmount = (Float) recurringDepositAccountData.get("mandatoryRecommendedDepositAmount"); + + /*** + * Perform Deposit transaction and verify journal entries are posted for + * the transaction + */ + todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -20); + + for (int i = 0; i < 14; i++) { + Integer depositTransactionId = this.recurringDepositAccountHelper.depositToRecurringDepositAccount(recurringDepositAccountId, + depositAmount, dateFormat.format(todaysDate.getTime())); + Assert.assertNotNull(depositTransactionId); + + this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, dateFormat.format(todaysDate.getTime()), new JournalEntry( + depositAmount, JournalEntry.TransactionType.DEBIT)); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccount, dateFormat.format(todaysDate.getTime()), + new JournalEntry(depositAmount, JournalEntry.TransactionType.CREDIT)); + todaysDate.add(Calendar.MONTH, 1); + } + + /*** + * FD account verify whether account is matured + */ + + SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec, responseSpec); + String JobName = "Update Deposit Accounts Maturity details"; + schedulerJobHelper.executeJob(JobName); + + HashMap accountDetails = this.recurringDepositAccountHelper.getRecurringDepositAccountById(this.requestSpec, this.responseSpec, + recurringDepositAccountId); + + HashMap summary = (HashMap) accountDetails.get("summary"); + Assert.assertNotNull(summary.get("totalWithholdTax")); + Float withHoldTax = (Float) summary.get("totalWithholdTax"); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(liabilityAccountForTax, CLOSED_ON_DATE, new JournalEntry(withHoldTax, + JournalEntry.TransactionType.CREDIT)); + + + recurringDepositAccountStatusHashMap = RecurringDepositAccountStatusChecker.getStatusOfRecurringDepositAccount(this.requestSpec, + this.responseSpec, recurringDepositAccountId.toString()); + RecurringDepositAccountStatusChecker.verifyRecurringDepositAccountIsMatured(recurringDepositAccountStatusHashMap); + + + } + + + /*** * Test case for Recurring Deposit Account premature closure with * transaction type ReInvest and Cash Based accounting enabled @@ -2671,6 +3053,21 @@ public class RecurringDepositTest { final String recurringDepositProductJSON = recurringDepositProductHelper.withPeriodRangeChart().build(validFrom, validTo); return RecurringDepositProductHelper.createRecurringDepositProduct(recurringDepositProductJSON, requestSpec, responseSpec); } + + private Integer createRecurringDepositProductWithWithHoldTax(final String validFrom, final String validTo,final String taxGroupId, final String accountingRule, + Account... accounts) { + System.out.println("------------------------------CREATING NEW RECURRING DEPOSIT PRODUCT ---------------------------------------"); + RecurringDepositProductHelper recurringDepositProductHelper = new RecurringDepositProductHelper(this.requestSpec, this.responseSpec); + if (accountingRule.equals(CASH_BASED)) { + recurringDepositProductHelper = recurringDepositProductHelper.withAccountingRuleAsCashBased(accounts); + } else if (accountingRule.equals(NONE)) { + recurringDepositProductHelper = recurringDepositProductHelper.withAccountingRuleAsNone(); + } + final String recurringDepositProductJSON = recurringDepositProductHelper.withPeriodRangeChart() + .withWithHoldTax(taxGroupId)// + .build(validFrom, validTo); + return RecurringDepositProductHelper.createRecurringDepositProduct(recurringDepositProductJSON, requestSpec, responseSpec); + } private Integer createRecurringDepositProduct(final String validFrom, final String validTo, final String accountingRule, final String chartToBePicked, Account... accounts) { @@ -2793,6 +3190,13 @@ public class RecurringDepositTest { Assert.assertEquals(financialActivityId, ((HashMap) mappingDetails.get("financialActivityData")).get("id")); Assert.assertEquals(glAccount.getAccountID(), ((HashMap) mappingDetails.get("glAccountData")).get("id")); } + + private Integer createTaxGroup(final String percentage, final Account liabilityAccountForTax){ + final Integer liabilityAccountId = liabilityAccountForTax.getAccountID(); + final Integer taxComponentId = TaxComponentHelper.createTaxComponent(this.requestSpec, this.responseSpec, percentage, liabilityAccountId); + return TaxGroupHelper.createTaxGroup(this.requestSpec, this.responseSpec, Arrays.asList(taxComponentId)); + } + /** * Delete the Liability transfer account diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/TaxComponentHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/TaxComponentHelper.java new file mode 100644 index 000000000..80c3690a7 --- /dev/null +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/TaxComponentHelper.java @@ -0,0 +1,64 @@ +/** + * 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.integrationtests.common; + +import java.util.HashMap; + +import org.apache.fineract.integrationtests.common.accounting.Account; + +import com.google.gson.Gson; +import com.jayway.restassured.specification.RequestSpecification; +import com.jayway.restassured.specification.ResponseSpecification; + +public class TaxComponentHelper { + + private static final String CREATE_TAX_COMPONENT_URL = "/fineract-provider/api/v1/taxes/component?" + Utils.TENANT_IDENTIFIER; + + public static Integer createTaxComponent(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + final String percentage, final Integer liabilityAccountId) { + System.out.println("---------------------------------CREATING A TAX COMPONENT---------------------------------------------"); + return Utils.performServerPost(requestSpec, responseSpec, CREATE_TAX_COMPONENT_URL, + getTaxComponentAsJSON(percentage, liabilityAccountId), "resourceId"); + } + + public static String getTaxComponentAsJSON(final String percentage, final Integer creditAccountId) { + final HashMap map = getBasicTaxComponentMap(percentage); + if (creditAccountId != null) { + map.put("creditAccountType", Account.AccountType.LIABILITY.toString()); + map.put("creditAcountId", String.valueOf(creditAccountId)); + } + System.out.println("map : " + map); + return new Gson().toJson(map); + } + + public static HashMap getBasicTaxComponentMap(final String percentage) { + final HashMap map = new HashMap<>(); + map.put("name", randomNameGenerator("Tax_component_Name_", 5)); + map.put("dateFormat", "dd MMMM yyyy"); + map.put("locale", "en"); + map.put("percentage", percentage); + map.put("startDate", "01 January 2013"); + return map; + } + + public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) { + return Utils.randomStringGenerator(prefix, lenOfRandomSuffix); + } + +} \ No newline at end of file diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/TaxGroupHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/TaxGroupHelper.java new file mode 100644 index 000000000..f0faa0fd5 --- /dev/null +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/TaxGroupHelper.java @@ -0,0 +1,68 @@ +/** + * 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.integrationtests.common; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +import com.google.gson.Gson; +import com.jayway.restassured.specification.RequestSpecification; +import com.jayway.restassured.specification.ResponseSpecification; + +public class TaxGroupHelper { + + private static final String CREATE_TAX_COMPONENT_URL = "/fineract-provider/api/v1/taxes/group?" + Utils.TENANT_IDENTIFIER; + + public static Integer createTaxGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + final Collection taxComponentIds) { + System.out.println("---------------------------------CREATING A TAX GROUP---------------------------------------------"); + return Utils.performServerPost(requestSpec, responseSpec, CREATE_TAX_COMPONENT_URL, getTaxGroupAsJSON(taxComponentIds), "resourceId"); + } + + public static String getTaxGroupAsJSON(final Collection taxComponentIds) { + final HashMap map = new HashMap<>(); + map.put("name", randomNameGenerator("Tax_component_Name_", 5)); + map.put("dateFormat", "dd MMMM yyyy"); + map.put("locale", "en"); + map.put("taxComponents", getTaxGroupComponents(taxComponentIds)); + return new Gson().toJson(map); + } + + public static List> getTaxGroupComponents(final Collection taxComponentIds) { + List> taxGroupComponents = new ArrayList<>(); + for (Integer taxComponentId : taxComponentIds) { + taxGroupComponents.add(getTaxComponentMap(taxComponentId)); + } + return taxGroupComponents; + } + + public static HashMap getTaxComponentMap(final Integer taxComponentId) { + final HashMap map = new HashMap<>(); + map.put("taxComponentId", String.valueOf(taxComponentId)); + map.put("startDate", "01 January 2013"); + return map; + } + + public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) { + return Utils.randomStringGenerator(prefix, lenOfRandomSuffix); + } + +} \ No newline at end of file diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java index b5497637c..6bd4579cf 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.HashMap; import org.apache.fineract.integrationtests.common.Utils; +import org.junit.Assert; import com.jayway.restassured.specification.RequestSpecification; import com.jayway.restassured.specification.ResponseSpecification; @@ -81,9 +82,17 @@ public class JournalEntryHelper { private void checkJournalEntry(final Integer officeId, final Account account, final String date, final JournalEntry... accountEntries) { final String url = createURLForGettingAccountEntries(account, date, officeId); final ArrayList response = Utils.performServerGet(this.requestSpec, this.responseSpec, url, "pageItems"); - for (int i = 0; i < accountEntries.length; i++) { - assertThat(getEntryValueFromJournalEntry(response, i), equalTo(accountEntries[i].getTransactionType())); - assertThat(getTransactionAmountFromJournalEntry(response, i), equalTo(accountEntries[i].getTransactionAmount())); + + for (JournalEntry entry : accountEntries) { + boolean matchFound = false; + for (HashMap map : response) { + final HashMap entryType = (HashMap) map.get("entryType"); + if (entry.getTransactionType().equals(entryType.get("value")) && entry.getTransactionAmount().equals(map.get("amount"))) { + matchFound = true; + break; + } + } + Assert.assertTrue("Journal Entry not found", matchFound); } } diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountStatusChecker.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountStatusChecker.java index fca5cec4f..c276af5e2 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountStatusChecker.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountStatusChecker.java @@ -87,6 +87,12 @@ public class FixedDepositAccountStatusChecker { assertTrue("ERROR IN PREMATURELY CLOSING THE FIXED DEPOSIT ACCOUNT", getStatus(fixedDepositStatusHashMap, "prematureClosed")); System.out.println(fixedDepositStatusHashMap); } + + public static void verifyFixedDepositAccountIsMatured(HashMap fixedDepositStatusHashMap) { + System.out.println("--------------------- VERIFYING FIXED DEPOSIT APPLICATION IS MATURED ---------------------"); + assertTrue("ERROR IN MATURITY JOB OF THE FIXED DEPOSIT ACCOUNT", getStatus(fixedDepositStatusHashMap, "matured")); + System.out.println(fixedDepositStatusHashMap); + } private static boolean getStatus(final HashMap fixedDepositStatusMap, final String fixedDepositStatusString) { return (Boolean) fixedDepositStatusMap.get(fixedDepositStatusString); diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java index b20b6f96e..f2bb04de3 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java @@ -74,6 +74,7 @@ public class FixedDepositProductHelper { private static final String ACCRUAL_UPFRONT = "4"; private static final String WHOLE_TERM = "1"; private static final String TILL_PREMATURE_WITHDRAWAL = "2"; + private String name = Utils.randomNameGenerator("FIXED_DEPOSIT_PRODUCT_", 6); private String shortName = Utils.randomNameGenerator("", 4); @@ -99,6 +100,8 @@ public class FixedDepositProductHelper { private Account[] accountList = null; private List> chartSlabs = null; private boolean isPrimaryGroupingByAmount = false; + private Boolean withHoldTax = false; + private String taxGroupId = null; public String build(final String validFrom, final String validTo) { final HashMap map = new HashMap<>(); @@ -139,6 +142,11 @@ public class FixedDepositProductHelper { map.put("inMultiplesOfDepositTermTypeId", this.inMultiplesOfDepositTermTypeId); map.put("preClosurePenalInterest", this.preClosurePenalInterest); map.put("preClosurePenalInterestOnTypeId", this.preClosurePenalInterestOnTypeId); + map.put("withHoldTax", this.withHoldTax.toString()); + if (withHoldTax) { + map.put("taxGroupId", taxGroupId); + } + if (this.accountingRule.equals(CASH_BASED)) { @@ -381,6 +389,15 @@ public class FixedDepositProductHelper { this.chartSlabs = constructChartSlabWithAmountAndPeriodRange(); return this; } + + public FixedDepositProductHelper withWithHoldTax(final String taxGroupId) { + if (taxGroupId != null) { + this.withHoldTax = true; + this.taxGroupId = taxGroupId; + } + return this; + } + private Map getAccountMappingForCashBased() { final Map map = new HashMap<>(); diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositAccountStatusChecker.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositAccountStatusChecker.java index 95cd69eb8..ae8a5a5e8 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositAccountStatusChecker.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositAccountStatusChecker.java @@ -89,6 +89,12 @@ public class RecurringDepositAccountStatusChecker { getStatus(recurringDepositStatusHashMap, "prematureClosed")); System.out.println(recurringDepositStatusHashMap); } + + public static void verifyRecurringDepositAccountIsMatured(HashMap recurringDepositStatusHashMap) { + System.out.println("--------------------- VERIFYING RECURRING DEPOSIT APPLICATION IS MATURED ---------------------"); + assertTrue("ERROR IN MATURITY JOB OF THE RECURRING DEPOSIT ACCOUNT", getStatus(recurringDepositStatusHashMap, "matured")); + System.out.println(recurringDepositStatusHashMap); + } private static boolean getStatus(final HashMap recurringDepositStatusMap, final String recurringDepositStatusString) { return (Boolean) recurringDepositStatusMap.get(recurringDepositStatusString); diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java index 16dc1d278..9114d6bb6 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/recurringdeposit/RecurringDepositProductHelper.java @@ -98,6 +98,8 @@ public class RecurringDepositProductHelper { private Account[] accountList = null; private List> chartSlabs = null; private boolean isPrimaryGroupingByAmount = false; + private Boolean withHoldTax = false; + private String taxGroupId = null; public String build(final String validFrom, final String validTo) { final HashMap map = new HashMap<>(); @@ -144,6 +146,10 @@ public class RecurringDepositProductHelper { map.put("depositAmount", this.depositAmount); map.put("minDepositAmount", this.minDepositAmount); map.put("maxDepositAmount", this.maxDepositAmount); + map.put("withHoldTax", this.withHoldTax.toString()); + if (withHoldTax) { + map.put("taxGroupId", taxGroupId); + } if (this.accountingRule.equals(CASH_BASED)) { map.putAll(getAccountMappingForCashBased()); @@ -184,6 +190,15 @@ public class RecurringDepositProductHelper { this.chartSlabs = constructChartSlabWithAmountAndPeriodRange(); return this; } + + public RecurringDepositProductHelper withWithHoldTax(final String taxGroupId) { + if (taxGroupId != null) { + this.withHoldTax = true; + this.taxGroupId = taxGroupId; + } + return this; + } + private Map getAccountMappingForCashBased() { final Map map = new HashMap<>(); diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java index e78a9cd9b..762473833 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java @@ -48,6 +48,7 @@ public class SavingsAccountHelper { private static final String CALCULATE_INTEREST_SAVINGS_COMMAND = "calculateInterest"; private static final String POST_INTEREST_SAVINGS_COMMAND = "postInterest"; private static final String CLOSE_SAVINGS_COMMAND = "close"; + private static final String UPDATE_WITHHOLD_TAX_STATUS = "updateWithHoldTax"; private static final String DEPOSIT_SAVINGS_COMMAND = "deposit"; private static final String WITHDRAW_SAVINGS_COMMAND = "withdrawal"; @@ -96,6 +97,15 @@ public class SavingsAccountHelper { + Utils.TENANT_IDENTIFIER, savingsApplicationJSON, CommonConstants.RESPONSE_CHANGES); } + public HashMap updateSavingsAccountWithHoldTaxStatus(final Integer savingsId, final boolean value) { + final HashMap map = new HashMap<>(); + map.put("withHoldTax", value); + String json = new Gson().toJson(map); + + return Utils.performServerPut(this.requestSpec, this.responseSpec, SAVINGS_ACCOUNT_URL + "/" + savingsId + "?command=" + + UPDATE_WITHHOLD_TAX_STATUS + "&" + Utils.TENANT_IDENTIFIER, json, CommonConstants.RESPONSE_CHANGES); + } + public HashMap approveSavings(final Integer savingsID) { return approveSavingsOnDate(savingsID, null); } @@ -341,9 +351,9 @@ public class SavingsAccountHelper { final ArrayList response = Utils.performServerGet(requestSpec, responseSpec, URL, jSONAttribute); return response; } - - public Object getSavingsAccountDetail(final Integer savingsID, final String jsonAttribute){ - final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "?associations=all&" + Utils.TENANT_IDENTIFIER; + + public Object getSavingsAccountDetail(final Integer savingsID, final String jsonAttribute) { + final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "?associations=all&" + Utils.TENANT_IDENTIFIER; return Utils.performServerGet(requestSpec, responseSpec, URL, jsonAttribute); } diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java index d7a391226..0eca50b02 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java @@ -83,9 +83,11 @@ public class SavingsProductHelper { private Account[] accountList = null; private String minBalanceForInterestCalculation = null; private String allowOverdraft = "false"; - private String overdraftLimit = null; + private String overdraftLimit = null; private String minRequiredBalance = null; private String enforceMinRequiredBalance = "false"; + private Boolean withHoldTax = false; + private String taxGroupId = null; public String build() { final HashMap map = new HashMap<>(); @@ -121,7 +123,10 @@ public class SavingsProductHelper { map.put("overdraftLimit", this.overdraftLimit); map.put("minRequiredBalance", this.minRequiredBalance); map.put("enforceMinRequiredBalance", this.enforceMinRequiredBalance); - + map.put("withHoldTax", this.withHoldTax.toString()); + if (withHoldTax) { + map.put("taxGroupId", taxGroupId); + } if (this.accountingRule.equals(CASH_BASED)) { map.putAll(getAccountMappingForCashBased()); } @@ -144,7 +149,7 @@ public class SavingsProductHelper { this.minRequiredOpeningBalance = minBalance; return this; } - + public SavingsProductHelper withInterestCompoundingPeriodTypeAsMonthly() { this.interestCompoundingPeriodType = MONTHLY; return this; @@ -154,7 +159,7 @@ public class SavingsProductHelper { this.interestPostingPeriodType = MONTHLY; return this; } - + public SavingsProductHelper withMinBalanceForInterestCalculation(final String amount) { this.minBalanceForInterestCalculation = amount; return this; @@ -190,12 +195,12 @@ public class SavingsProductHelper { this.accountList = account_list; return this; } - + public SavingsProductHelper withMinRequiredBalance(String minBalance) { this.minRequiredBalance = minBalance; return this; } - + public SavingsProductHelper withEnforceMinRequiredBalance(String enforceMinRequiredBalance) { this.enforceMinRequiredBalance = enforceMinRequiredBalance; return this; @@ -207,6 +212,14 @@ public class SavingsProductHelper { return this; } + public SavingsProductHelper withWithHoldTax(final String taxGroupId) { + if (taxGroupId != null) { + this.withHoldTax = true; + this.taxGroupId = taxGroupId; + } + return this; + } + private Map getAccountMappingForCashBased() { final Map map = new HashMap<>(); if (accountList != null) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/SavingsTransactionDTO.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/SavingsTransactionDTO.java index 0ee7b11a0..567ef2db2 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/SavingsTransactionDTO.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/SavingsTransactionDTO.java @@ -41,13 +41,14 @@ public class SavingsTransactionDTO { /** Breakdowns of fees and penalties this Transaction pays **/ private final List penaltyPayments; private final List feePayments; + private final List taxPayments; private final boolean isAccountTransfer; public SavingsTransactionDTO(final Long officeId, final Long paymentTypeId, final String transactionId, final Date transactionDate, final SavingsAccountTransactionEnumData transactionType, final BigDecimal amount, final boolean reversed, final List feePayments, final List penaltyPayments, final BigDecimal overdraftAmount, - boolean isAccountTransfer) { + boolean isAccountTransfer, final List taxPayments) { this.paymentTypeId = paymentTypeId; this.transactionId = transactionId; this.transactionDate = transactionDate; @@ -59,6 +60,7 @@ public class SavingsTransactionDTO { this.officeId = officeId; this.overdraftAmount = overdraftAmount; this.isAccountTransfer = isAccountTransfer; + this.taxPayments = taxPayments; } public Long getOfficeId() { @@ -109,4 +111,8 @@ public class SavingsTransactionDTO { return this.isAccountTransfer; } + public List getTaxPayments() { + return this.taxPayments; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/TaxPaymentDTO.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/TaxPaymentDTO.java new file mode 100644 index 000000000..e9e1d34b9 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/TaxPaymentDTO.java @@ -0,0 +1,47 @@ +/** + * 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.data; + +import java.math.BigDecimal; + +public class TaxPaymentDTO { + + private final Long creditAccountId; + private final Long debitAccountId; + private final BigDecimal amount; + + public TaxPaymentDTO(final Long debitAccountId, final Long creditAccountId, final BigDecimal amount) { + this.debitAccountId = debitAccountId; + this.creditAccountId = creditAccountId; + this.amount = amount; + } + + public Long getCreditAccountId() { + return this.creditAccountId; + } + + public Long getDebitAccountId() { + return this.debitAccountId; + } + + public BigDecimal getAmount() { + return this.amount; + } + +} 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 575cd86fd..fb9c799ae 100755 --- 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 @@ -43,6 +43,7 @@ import org.apache.fineract.accounting.journalentry.data.LoanDTO; import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO; import org.apache.fineract.accounting.journalentry.data.SavingsDTO; import org.apache.fineract.accounting.journalentry.data.SavingsTransactionDTO; +import org.apache.fineract.accounting.journalentry.data.TaxPaymentDTO; import org.apache.fineract.accounting.journalentry.domain.JournalEntry; import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository; import org.apache.fineract.accounting.journalentry.domain.JournalEntryType; @@ -78,7 +79,7 @@ public class AccountingProcessorHelper { public static final String LOAN_TRANSACTION_IDENTIFIER = "L"; public static final String SAVINGS_TRANSACTION_IDENTIFIER = "S"; public static final String CLIENT_TRANSACTION_IDENTIFIER = "C"; - public static final String PROVISIONING_TRANSACTION_IDENTIFIER = "P" ; + public static final String PROVISIONING_TRANSACTION_IDENTIFIER = "P"; private final JournalEntryRepository glJournalEntryRepository; private final ProductToGLAccountMappingRepository accountMappingRepository; private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository; @@ -214,12 +215,26 @@ public class AccountingProcessorHelper { } } } + + final List taxPayments = new ArrayList<>(); + if (map.containsKey("taxDetails")) { + @SuppressWarnings("unchecked") + final List> taxDatas = (List>) map.get("taxDetails"); + for (final Map taxData : taxDatas) { + final BigDecimal taxAmount = (BigDecimal) taxData.get("amount"); + final Long creditAccountId = (Long) taxData.get("creditAccountId"); + final Long debitAccountId = (Long) taxData.get("debitAccountId"); + taxPayments.add(new TaxPaymentDTO(debitAccountId, creditAccountId, taxAmount)); + } + } + if (!isAccountTransfer) { isAccountTransfer = this.accountTransfersReadPlatformService.isAccountTransfer(Long.parseLong(transactionId), PortfolioAccountType.SAVINGS); } final SavingsTransactionDTO transaction = new SavingsTransactionDTO(transactionOfficeId, paymentTypeId, transactionId, - transactionDate, transactionType, amount, reversed, feePayments, penaltyPayments, overdraftAmount, isAccountTransfer); + transactionDate, transactionType, amount, reversed, feePayments, penaltyPayments, overdraftAmount, isAccountTransfer, + taxPayments); newSavingsTransactions.add(transaction); @@ -254,15 +269,14 @@ public class AccountingProcessorHelper { final boolean isPenalty = (Boolean) clientChargePaid.get("isPenalty"); final BigDecimal chargeAmountPaid = (BigDecimal) clientChargePaid.get("amount"); final Long incomeAccountId = (Long) clientChargePaid.get("incomeAccountId"); - final ClientChargePaymentDTO clientChargePaymentDTO = new ClientChargePaymentDTO(chargeId, chargeAmountPaid, clientChargeId, - isPenalty, incomeAccountId); + final ClientChargePaymentDTO clientChargePaymentDTO = new ClientChargePaymentDTO(chargeId, chargeAmountPaid, + clientChargeId, isPenalty, incomeAccountId); clientChargePaymentDTOs.add(clientChargePaymentDTO); } } final ClientTransactionDTO clientTransactionDTO = new ClientTransactionDTO(clientId, transactionOfficeId, paymentTypeId, - transactionId, transactionDate, transactionType, currencyCode, amount, reversed, accountingEnabled, - clientChargePaymentDTOs); + transactionId, transactionDate, transactionType, currencyCode, amount, reversed, accountingEnabled, clientChargePaymentDTOs); return clientTransactionDTO; @@ -289,8 +303,9 @@ public class AccountingProcessorHelper { * @param isReversal */ public void createAccrualBasedJournalEntriesAndReversalsForLoan(final Office office, final String currencyCode, - final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long paymentTypeId, - final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { + final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, + final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, + final Boolean isReversal) { int accountTypeToDebitId = accountTypeToBeDebited; int accountTypeToCreditId = accountTypeToBeCredited; // reverse debits and credits for reversals @@ -298,8 +313,8 @@ public class AccountingProcessorHelper { accountTypeToDebitId = accountTypeToBeCredited; accountTypeToCreditId = accountTypeToBeDebited; } - createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, loanId, - transactionId, transactionDate, amount); + createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, + loanId, transactionId, transactionDate, amount); } /** @@ -419,8 +434,9 @@ public class AccountingProcessorHelper { * @param isReversal */ public void createCashBasedJournalEntriesAndReversalsForLoan(final Office office, final String currencyCode, - final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, final Long paymentTypeId, - final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { + final Integer accountTypeToBeDebited, final Integer accountTypeToBeCredited, final Long loanProductId, + final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, + final Boolean isReversal) { int accountTypeToDebitId = accountTypeToBeDebited; int accountTypeToCreditId = accountTypeToBeCredited; // reverse debits and credits for reversals @@ -428,8 +444,8 @@ public class AccountingProcessorHelper { accountTypeToDebitId = accountTypeToBeCredited; accountTypeToCreditId = accountTypeToBeDebited; } - createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, loanId, - transactionId, transactionDate, amount); + createJournalEntriesForLoan(office, currencyCode, accountTypeToDebitId, accountTypeToCreditId, loanProductId, paymentTypeId, + loanId, transactionId, transactionDate, amount); } public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, @@ -458,9 +474,8 @@ public class AccountingProcessorHelper { * transaction Date **/ if (latestGLClosure != null) { - if (latestGLClosure.getClosingDate().after(transactionDate) - || latestGLClosure.getClosingDate().equals(transactionDate)) { throw new JournalEntryInvalidException( - GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } + if (latestGLClosure.getClosingDate().after(transactionDate) || latestGLClosure.getClosingDate().equals(transactionDate)) { throw new JournalEntryInvalidException( + GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate(), null, null); } } } @@ -490,6 +505,111 @@ public class AccountingProcessorHelper { createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount); } + /** + * Convenience method that creates a pair of related Debits and Credits for + * Cash Based accounting. + * + * The target accounts for debits and credits are switched in case of a + * reversal + * + * @param office + * @param accountTypeToBeDebited + * Enum of the placeholder GLAccount to be debited + * @param accountTypeToBeCredited + * Enum of the placeholder of the GLAccount to be credited + * @param savingsProductId + * @param paymentTypeId + * @param loanId + * @param transactionId + * @param transactionDate + * @param amount + * @param isReversal + */ + public void createCashBasedJournalEntriesAndReversalsForSavingsTax(final Office office, final String currencyCode, + final CASH_ACCOUNTS_FOR_SAVINGS accountTypeToBeDebited, final CASH_ACCOUNTS_FOR_SAVINGS accountTypeToBeCredited, + final Long savingsProductId, final Long paymentTypeId, final Long savingsId, final String transactionId, + final Date 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 Date transactionDate, final BigDecimal amount, final Boolean isReversal) { + // reverse debits and credits for reversals + if (isReversal) { + createCreditJournalEntriesForSavings(office, currencyCode, accountTypeToBeDebited, savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, amount); + } else { + createDebitJournalEntriesForSavings(office, currencyCode, accountTypeToBeDebited, savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, amount); + } + } + + public void createCashBasedCreditJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, + final Integer accountTypeToBeCredited, final Long savingsProductId, final Long paymentTypeId, final Long savingsId, + final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { + // reverse debits and credits for reversals + if (isReversal) { + createDebitJournalEntriesForSavings(office, currencyCode, accountTypeToBeCredited, savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, amount); + } else { + createCreditJournalEntriesForSavings(office, currencyCode, accountTypeToBeCredited, savingsProductId, paymentTypeId, savingsId, + transactionId, transactionDate, amount); + } + } + + public void createCashBasedDebitJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, + final Long debitAccountId, final Long savingsId, final String transactionId, final Date transactionDate, + final BigDecimal amount, final Boolean isReversal) { + // reverse debits and credits for reversals + final GLAccount debitAccount = getGLAccountById(debitAccountId); + if (isReversal) { + createCreditJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount); + } else { + createDebitJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount); + } + } + + public void createCashBasedCreditJournalEntriesAndReversalsForSavings(final Office office, final String currencyCode, + final Long creditAccountId, final Long savingsId, final String transactionId, final Date transactionDate, + final BigDecimal amount, final Boolean isReversal) { + // reverse debits and credits for reversals + final GLAccount creditAccount = getGLAccountById(creditAccountId); + if (isReversal) { + createDebitJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount); + } else { + createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount); + } + } + + private void createDebitJournalEntriesForSavings(final Office office, final String currencyCode, final int accountTypeToDebitId, + final Long savingsProductId, final Long paymentTypeId, final Long savingsId, final String transactionId, + final Date transactionDate, final BigDecimal amount) { + final GLAccount debitAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToDebitId, paymentTypeId); + createDebitJournalEntryForSavings(office, currencyCode, debitAccount, savingsId, transactionId, transactionDate, amount); + } + + private void createCreditJournalEntriesForSavings(final Office office, final String currencyCode, final int accountTypeToCreditId, + final Long savingsProductId, final Long paymentTypeId, final Long savingsId, final String transactionId, + final Date transactionDate, final BigDecimal amount) { + final GLAccount creditAccount = getLinkedGLAccountForSavingsProduct(savingsProductId, accountTypeToCreditId, paymentTypeId); + createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount); + } + public void createDebitJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final int accountMappingTypeId, final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { @@ -631,8 +751,8 @@ public class AccountingProcessorHelper { String modifiedTransactionId = transactionId.toString(); modifiedTransactionId = CLIENT_TRANSACTION_IDENTIFIER + transactionId; final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, - manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.CLIENT.getValue(), clientId, null, - loanTransaction, savingsAccountTransaction, clientTransaction); + manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.CLIENT.getValue(), clientId, + null, loanTransaction, savingsAccountTransaction, clientTransaction); this.glJournalEntryRepository.saveAndFlush(journalEntry); } @@ -655,8 +775,8 @@ public class AccountingProcessorHelper { this.glJournalEntryRepository.saveAndFlush(journalEntry); } - private void createCreditJournalEntryForLoan(final Office office, final String currencyCode, final GLAccount account, final Long loanId, - final String transactionId, final Date transactionDate, final BigDecimal amount) { + private void createCreditJournalEntryForLoan(final Office office, final String currencyCode, final GLAccount account, + final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount) { final boolean manualEntry = false; LoanTransaction loanTransaction = null; SavingsAccountTransaction savingsAccountTransaction = null; @@ -674,33 +794,35 @@ public class AccountingProcessorHelper { this.glJournalEntryRepository.saveAndFlush(journalEntry); } - public void createProvisioningDebitJournalEntry(Date transactionDate, Long provisioningentryId, Office office, String currencyCode, GLAccount account,BigDecimal amount) { + public void createProvisioningDebitJournalEntry(Date transactionDate, Long provisioningentryId, Office office, String currencyCode, + GLAccount account, BigDecimal amount) { LoanTransaction loanTransaction = null; SavingsAccountTransaction savingsAccountTransaction = null; ClientTransaction clientTransaction = null; - PaymentDetail paymentDetail = null ; + PaymentDetail paymentDetail = null; final boolean manualEntry = false; String modifiedTransactionId = PROVISIONING_TRANSACTION_IDENTIFIER + provisioningentryId; final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, - manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.PROVISIONING.getValue(), provisioningentryId, null, - loanTransaction, savingsAccountTransaction, clientTransaction); - + manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.PROVISIONING.getValue(), + provisioningentryId, null, loanTransaction, savingsAccountTransaction, clientTransaction); + this.glJournalEntryRepository.saveAndFlush(journalEntry); } - - public void createProvisioningCreditJournalEntry(Date transactionDate, Long provisioningentryId, Office office, String currencyCode, GLAccount account, BigDecimal amount) { + + public void createProvisioningCreditJournalEntry(Date transactionDate, Long provisioningentryId, Office office, String currencyCode, + GLAccount account, BigDecimal amount) { LoanTransaction loanTransaction = null; SavingsAccountTransaction savingsAccountTransaction = null; ClientTransaction clientTransaction = null; - PaymentDetail paymentDetail = null ; + PaymentDetail paymentDetail = null; final boolean manualEntry = false; String modifiedTransactionId = PROVISIONING_TRANSACTION_IDENTIFIER + provisioningentryId; final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, - manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.PROVISIONING.getValue(), provisioningentryId, null, - loanTransaction, savingsAccountTransaction, clientTransaction); + manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.PROVISIONING.getValue(), + provisioningentryId, null, loanTransaction, savingsAccountTransaction, clientTransaction); this.glJournalEntryRepository.saveAndFlush(journalEntry); } - + private void createDebitJournalEntryForLoan(final Office office, final String currencyCode, final GLAccount account, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount) { final boolean manualEntry = false; @@ -734,8 +856,8 @@ public class AccountingProcessorHelper { modifiedTransactionId = SAVINGS_TRANSACTION_IDENTIFIER + transactionId; } final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId, - manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null, - loanTransaction, savingsAccountTransaction, clientTransaction); + manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, + null, loanTransaction, savingsAccountTransaction, clientTransaction); this.glJournalEntryRepository.saveAndFlush(journalEntry); } @@ -791,13 +913,12 @@ public class AccountingProcessorHelper { private GLAccount getLinkedGLAccountForLoanCharges(final Long loanProductId, final int accountMappingTypeId, final Long chargeId) { ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(loanProductId, PortfolioProductType.LOAN.getValue(), accountMappingTypeId); - /***** - * Get more specific mappings for Charges and penalties (based - * on the actual charge /penalty coupled with the loan product). - * Note the income from fees and income from penalties - * placeholder ID would be the same for both cash and accrual - * based accounts - *****/ + /***** + * Get more specific mappings for Charges and penalties (based on the + * actual charge /penalty coupled with the loan product). Note the + * income from fees and income from penalties placeholder ID would be + * the same for both cash and accrual based accounts + *****/ // Vishwas TODO: remove this condition as it should always be true if (accountMappingTypeId == CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue() @@ -812,17 +933,15 @@ public class AccountingProcessorHelper { return accountMapping.getGlAccount(); } - private GLAccount getLinkedGLAccountForSavingsCharges(final Long savingsProductId, final int accountMappingTypeId, - final Long chargeId) { + private GLAccount getLinkedGLAccountForSavingsCharges(final Long savingsProductId, final int accountMappingTypeId, final Long chargeId) { ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(savingsProductId, PortfolioProductType.SAVING.getValue(), accountMappingTypeId); - /***** - * Get more specific mappings for Charges and penalties (based - * on the actual charge /penalty coupled with the loan product). - * Note the income from fees and income from penalties - * placeholder ID would be the same for both cash and accrual - * based accounts - *****/ + /***** + * Get more specific mappings for Charges and penalties (based on the + * actual charge /penalty coupled with the loan product). Note the + * income from fees and income from penalties placeholder ID would be + * the same for both cash and accrual based accounts + *****/ // Vishwas TODO: remove this condition as it should always be true if (accountMappingTypeId == CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_FEES.getValue() @@ -912,8 +1031,8 @@ public class AccountingProcessorHelper { public void createDebitJournalEntryOrReversalForClientChargePayments(final Office office, final String currencyCode, final Long clientId, final Long transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { - final GLAccount account = financialActivityAccountRepository - .findByFinancialActivityTypeWithNotFoundDetection(FINANCIAL_ACTIVITY.ASSET_FUND_SOURCE.getValue()).getGlAccount(); + final GLAccount account = financialActivityAccountRepository.findByFinancialActivityTypeWithNotFoundDetection( + FINANCIAL_ACTIVITY.ASSET_FUND_SOURCE.getValue()).getGlAccount(); if (isReversal) { createCreditJournalEntryForClientPayments(office, currencyCode, account, clientId, transactionId, transactionDate, amount); } else { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java index 2b8198a49..27eb9e8dd 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java @@ -168,6 +168,13 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess } } + else if (savingsTransactionDTO.getTransactionType().isWithholdTax()) { + this.helper.createCashBasedJournalEntriesAndReversalsForSavingsTax(office, currencyCode, + CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL, CASH_ACCOUNTS_FOR_SAVINGS.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()) { // Is the Charge a penalty? diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index d53204956..cc33ecc14 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -2616,4 +2616,44 @@ public class CommandWrapperBuilder { this.href = "/accounts/" + accountType+"/"+accountId; return this; } + + public CommandWrapperBuilder createTaxComponent() { + this.actionName = "CREATE"; + this.entityName = "TAXCOMPONENT"; + this.entityId = null; + this.href = "/taxes/component"; + return this; + } + + public CommandWrapperBuilder updateTaxComponent(final Long taxComponentId) { + this.actionName = "UPDATE"; + this.entityName = "TAXCOMPONENT"; + this.entityId = taxComponentId; + this.href = "/taxes/component/" + taxComponentId; + return this; + } + + public CommandWrapperBuilder createTaxGroup() { + this.actionName = "CREATE"; + this.entityName = "TAXGROUP"; + this.entityId = null; + this.href = "/taxes/group"; + return this; + } + + public CommandWrapperBuilder updateTaxGroup(final Long taxGroupId) { + this.actionName = "UPDATE"; + this.entityName = "TAXGROUP"; + this.entityId = taxGroupId; + this.href = "/taxes/group/" + taxGroupId; + return this; + } + + public CommandWrapperBuilder updateWithHoldTax(final Long accountId) { + this.actionName = "UPDATEWITHHOLDTAX"; + this.entityName = "SAVINGSACCOUNT"; + this.entityId = accountId; + this.href = "/savingsaccounts/" + accountId + "?commad=updateTaxWithHoldTax"; + return this; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiConstants.java index 01488dd94..7e3526e29 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/api/ChargesApiConstants.java @@ -21,5 +21,6 @@ package org.apache.fineract.portfolio.charge.api; public class ChargesApiConstants { public static final String glAccountIdParamName = "incomeAccountId"; + public static final String taxGroupIdParamName = "taxGroupId"; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java index a818bd38a..28bcd74bb 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java @@ -30,6 +30,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.joda.time.LocalDate; import org.joda.time.MonthDay; @@ -54,6 +55,7 @@ public class ChargeData implements Comparable, Serializable { private final BigDecimal maxCap; private final EnumOptionData feeFrequency; private final GLAccountData incomeOrLiabilityAccount; + private final TaxGroupData taxGroup; private final Collection currencyOptions; private final List chargeCalculationTypeOptions;// @@ -70,6 +72,7 @@ public class ChargeData implements Comparable, Serializable { private final List feeFrequencyOptions; private final Map> incomeOrLiabilityAccountOptions; + private final Collection taxGroupOptions; public static ChargeData template(final Collection currencyOptions, final List chargeCalculationTypeOptions, final List chargeAppliesToOptions, @@ -77,31 +80,33 @@ public class ChargeData implements Comparable, Serializable { final List loansChargeCalculationTypeOptions, final List loansChargeTimeTypeOptions, final List savingsChargeCalculationTypeOptions, final List savingsChargeTimeTypeOptions, final List clientChargeCalculationTypeOptions, final List clientChargeTimeTypeOptions, - final List feeFrequencyOptions, final Map> incomeOrLiabilityAccountOptions) { + final List feeFrequencyOptions, final Map> incomeOrLiabilityAccountOptions, + final Collection taxGroupOptions) { final GLAccountData account = null; - return new ChargeData(null, null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions, - chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, - loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, - clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, null, null, null, null, null, feeFrequencyOptions, account, - incomeOrLiabilityAccountOptions); + final TaxGroupData taxGroupData = null; + return new ChargeData(null, null, null, null, null, null, null, null, false, false, taxGroupData, currencyOptions, + chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, + loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, + savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, null, null, null, null, + null, feeFrequencyOptions, account, incomeOrLiabilityAccountOptions, taxGroupOptions); } public static ChargeData withTemplate(final ChargeData charge, final ChargeData template) { return new ChargeData(charge.id, charge.name, charge.amount, charge.currency, charge.chargeTimeType, charge.chargeAppliesTo, - charge.chargeCalculationType, charge.chargePaymentMode, charge.penalty, charge.active, template.currencyOptions, - template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, template.chargeTimeTypeOptions, - template.chargePaymetModeOptions, template.loanChargeCalculationTypeOptions, template.loanChargeTimeTypeOptions, - template.savingsChargeCalculationTypeOptions, template.savingsChargeTimeTypeOptions, - template.clientChargeCalculationTypeOptions, template.clientChargeTimeTypeOptions, charge.feeOnMonthDay, charge.feeInterval, - charge.minCap, charge.maxCap, charge.feeFrequency, template.feeFrequencyOptions, charge.incomeOrLiabilityAccount, - template.incomeOrLiabilityAccountOptions); + charge.chargeCalculationType, charge.chargePaymentMode, charge.penalty, charge.active, charge.taxGroup, + template.currencyOptions, template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, + template.chargeTimeTypeOptions, template.chargePaymetModeOptions, template.loanChargeCalculationTypeOptions, + template.loanChargeTimeTypeOptions, template.savingsChargeCalculationTypeOptions, template.savingsChargeTimeTypeOptions, + template.clientChargeCalculationTypeOptions, template.clientChargeTimeTypeOptions, charge.feeOnMonthDay, + charge.feeInterval, charge.minCap, charge.maxCap, charge.feeFrequency, template.feeFrequencyOptions, + charge.incomeOrLiabilityAccount, template.incomeOrLiabilityAccountOptions, template.taxGroupOptions); } public static ChargeData instance(final Long id, final String name, final BigDecimal amount, final CurrencyData currency, final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType, final EnumOptionData chargePaymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final boolean penalty, final boolean active, final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency, - final GLAccountData accountData) { + final GLAccountData accountData, TaxGroupData taxGroupData) { final Collection currencyOptions = null; final List chargeCalculationTypeOptions = null; @@ -116,13 +121,14 @@ public class ChargeData implements Comparable, Serializable { final List clientChargeCalculationTypeOptions = null; final List clientChargeTimeTypeOptions = null; final Map> incomeOrLiabilityAccountOptions = null; + final Collection taxGroupOptions = null; return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, - penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, - chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, + penalty, active, taxGroupData, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, + chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions, accountData, - incomeOrLiabilityAccountOptions); + incomeOrLiabilityAccountOptions, taxGroupOptions); } public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) { @@ -153,17 +159,19 @@ public class ChargeData implements Comparable, Serializable { final List feeFrequencyOptions = null; final GLAccountData account = null; final Map> incomeOrLiabilityAccountOptions = null; + final TaxGroupData taxGroupData = null; + final Collection taxGroupOptions = null; return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, - penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, - chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, + penalty, active, taxGroupData, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, + chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions, account, - incomeOrLiabilityAccountOptions); + incomeOrLiabilityAccountOptions, taxGroupOptions); } private ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency, final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType, - final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active, + final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active, final TaxGroupData taxGroupData, final Collection currencyOptions, final List chargeCalculationTypeOptions, final List chargeAppliesToOptions, final List chargeTimeTypeOptions, final List chargePaymentModeOptions, final List loansChargeCalculationTypeOptions, @@ -172,7 +180,7 @@ public class ChargeData implements Comparable, Serializable { final List clientChargeTimeTypeOptions, final MonthDay feeOnMonthDay, final Integer feeInterval, final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency, final List feeFrequencyOptions, final GLAccountData account, - final Map> incomeOrLiabilityAccountOptions) { + final Map> incomeOrLiabilityAccountOptions, final Collection taxGroupOptions) { this.id = id; this.name = name; this.amount = amount; @@ -202,6 +210,8 @@ public class ChargeData implements Comparable, Serializable { this.feeFrequencyOptions = feeFrequencyOptions; this.incomeOrLiabilityAccount = account; this.incomeOrLiabilityAccountOptions = incomeOrLiabilityAccountOptions; + this.taxGroup = taxGroupData; + this.taxGroupOptions = taxGroupOptions; } @Override @@ -249,9 +259,9 @@ public class ChargeData implements Comparable, Serializable { final LocalDate inactivationDate = null; return SavingsAccountChargeData.instance(savingsChargeId, this.id, savingsAccountId, this.name, this.currency, this.amount, - amountPaid, amountWaived, amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, this.chargeCalculationType, - percentage, amountPercentageAppliedTo, chargeOptions, this.penalty, this.feeOnMonthDay, this.feeInterval, isActive, - inactivationDate); + amountPaid, amountWaived, amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, + this.chargeCalculationType, percentage, amountPercentageAppliedTo, chargeOptions, this.penalty, this.feeOnMonthDay, + this.feeInterval, isActive, inactivationDate); } public boolean isPenalty() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java index 7f8a16163..300884982 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java @@ -49,6 +49,8 @@ import org.apache.fineract.portfolio.charge.exception.ChargeMustBePenaltyExcepti import org.apache.fineract.portfolio.charge.exception.ChargeParameterUpdateNotSupportedException; import org.apache.fineract.portfolio.charge.service.ChargeEnumerations; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; import org.joda.time.MonthDay; import org.springframework.data.jpa.domain.AbstractPersistable; @@ -108,7 +110,11 @@ public class Charge extends AbstractPersistable { @JoinColumn(name = "income_or_liability_account_id") private GLAccount account; - public static Charge fromJson(final JsonCommand command, final GLAccount account) { + @ManyToOne + @JoinColumn(name = "tax_group_id") + private TaxGroup taxGroup; + + public static Charge fromJson(final JsonCommand command, final GLAccount account, final TaxGroup taxGroup) { final String name = command.stringValueOfParameterNamed("name"); final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount"); @@ -116,8 +122,8 @@ public class Charge extends AbstractPersistable { final ChargeAppliesTo chargeAppliesTo = ChargeAppliesTo.fromInt(command.integerValueOfParameterNamed("chargeAppliesTo")); final ChargeTimeType chargeTimeType = ChargeTimeType.fromInt(command.integerValueOfParameterNamed("chargeTimeType")); - final ChargeCalculationType chargeCalculationType = ChargeCalculationType - .fromInt(command.integerValueOfParameterNamed("chargeCalculationType")); + final ChargeCalculationType chargeCalculationType = ChargeCalculationType.fromInt(command + .integerValueOfParameterNamed("chargeCalculationType")); final Integer chargePaymentMode = command.integerValueOfParameterNamed("chargePaymentMode"); final ChargePaymentMode paymentMode = chargePaymentMode == null ? null : ChargePaymentMode.fromInt(chargePaymentMode); @@ -131,7 +137,7 @@ public class Charge extends AbstractPersistable { final Integer feeFrequency = command.integerValueOfParameterNamed("feeFrequency"); return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTimeType, chargeCalculationType, penalty, active, paymentMode, - feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, account); + feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, account, taxGroup); } protected Charge() { @@ -139,9 +145,9 @@ public class Charge extends AbstractPersistable { } private Charge(final String name, final BigDecimal amount, final String currencyCode, final ChargeAppliesTo chargeAppliesTo, - final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty, final boolean active, - final ChargePaymentMode paymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final BigDecimal minCap, - final BigDecimal maxCap, final Integer feeFrequency, final GLAccount account) { + final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty, + final boolean active, final ChargePaymentMode paymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, + final BigDecimal minCap, final BigDecimal maxCap, final Integer feeFrequency, final GLAccount account, final TaxGroup taxGroup) { this.name = name; this.amount = amount; this.currencyCode = currencyCode; @@ -151,6 +157,7 @@ public class Charge extends AbstractPersistable { this.penalty = penalty; this.active = active; this.account = account; + this.taxGroup = taxGroup; this.chargePaymentMode = paymentMode == null ? null : paymentMode.getValue(); final List dataValidationErrors = new ArrayList<>(); @@ -185,8 +192,8 @@ public class Charge extends AbstractPersistable { } else if (isLoanCharge()) { - if (penalty && (chargeTime.isTimeOfDisbursement() - || chargeTime.isTrancheDisbursement())) { throw new ChargeDueAtDisbursementCannotBePenaltyException(name); } + if (penalty && (chargeTime.isTimeOfDisbursement() || chargeTime.isTrancheDisbursement())) { throw new ChargeDueAtDisbursementCannotBePenaltyException( + name); } if (!penalty && chargeTime.isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); } // TODO vishwas, this validation seems unnecessary as identical // validation is performed in the write service @@ -487,16 +494,23 @@ public class Charge extends AbstractPersistable { } - if (this.penalty && ChargeTimeType.fromInt(this.chargeTimeType) - .isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(this.name); } - if (!penalty - && ChargeTimeType.fromInt(this.chargeTimeType).isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); } + if (this.penalty && ChargeTimeType.fromInt(this.chargeTimeType).isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException( + this.name); } + if (!penalty && ChargeTimeType.fromInt(this.chargeTimeType).isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); } if (command.isChangeInLongParameterNamed(ChargesApiConstants.glAccountIdParamName, getIncomeAccountId())) { final Long newValue = command.longValueOfParameterNamed(ChargesApiConstants.glAccountIdParamName); actualChanges.put(ChargesApiConstants.glAccountIdParamName, newValue); } + if (command.isChangeInLongParameterNamed(ChargesApiConstants.taxGroupIdParamName, getTaxGroupId())) { + final Long newValue = command.longValueOfParameterNamed(ChargesApiConstants.taxGroupIdParamName); + actualChanges.put(ChargesApiConstants.taxGroupIdParamName, newValue); + if(taxGroup != null){ + baseDataValidator.reset().parameter(ChargesApiConstants.taxGroupIdParamName).failWithCode("modification.not.supported"); + } + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } return actualChanges; @@ -524,10 +538,15 @@ public class Charge extends AbstractPersistable { if (account != null) { accountData = new GLAccountData(account.getId(), account.getName(), account.getGlCode()); } + TaxGroupData taxGroupData = null; + if (this.taxGroup != null) { + taxGroupData = TaxGroupData.lookup(taxGroup.getId(), taxGroup.getName()); + } + final CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null); return ChargeData.instance(getId(), this.name, this.amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentmode, getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active, this.minCap, this.maxCap, - feeFrequencyType, accountData); + feeFrequencyType, accountData, taxGroupData); } public Integer getChargePaymentMode() { @@ -582,8 +601,24 @@ public class Charge extends AbstractPersistable { return incomeAccountId; } + private Long getTaxGroupId() { + Long taxGroupId = null; + if (this.taxGroup != null) { + taxGroupId = this.taxGroup.getId(); + } + return taxGroupId; + } + public boolean isDisbursementCharge() { return ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.DISBURSEMENT) || ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.TRANCHE_DISBURSEMENT); } + + public TaxGroup getTaxGroup() { + return this.taxGroup; + } + + public void setTaxGroup(TaxGroup taxGroup) { + this.taxGroup = taxGroup; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java index c8a90e48b..bbf18ba66 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java @@ -56,7 +56,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer { private final Set supportedParameters = new HashSet<>(Arrays.asList("name", "amount", "locale", "currencyCode", "currencyOptions", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "penalty", "active", "chargePaymentMode", "feeOnMonthDay", "feeInterval", "monthDayFormat", "minCap", "maxCap", "feeFrequency", - ChargesApiConstants.glAccountIdParamName)); + ChargesApiConstants.glAccountIdParamName, ChargesApiConstants.taxGroupIdParamName)); private final FromJsonHelper fromApiJsonHelper; @@ -206,6 +206,11 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer { baseDataValidator.reset().parameter("maxCap").value(maxCap).notNull().positiveAmount(); } + if (this.fromApiJsonHelper.parameterExists(ChargesApiConstants.taxGroupIdParamName, element)) { + final Long taxGroupId = this.fromApiJsonHelper.extractLongNamed(ChargesApiConstants.taxGroupIdParamName, element); + baseDataValidator.reset().parameter(ChargesApiConstants.taxGroupIdParamName).value(taxGroupId).notNull().longGreaterThanZero(); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -316,6 +321,11 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer { .longGreaterThanZero(); } + if (this.fromApiJsonHelper.parameterExists(ChargesApiConstants.taxGroupIdParamName, element)) { + final Long taxGroupId = this.fromApiJsonHelper.extractLongNamed(ChargesApiConstants.taxGroupIdParamName, element); + baseDataValidator.reset().parameter(ChargesApiConstants.taxGroupIdParamName).value(taxGroupId).notNull().longGreaterThanZero(); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java index a1ae0bc31..542e4714a 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java @@ -41,6 +41,8 @@ import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; import org.apache.fineract.portfolio.charge.exception.ChargeNotFoundException; import org.apache.fineract.portfolio.common.service.CommonEnumerations; import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; +import org.apache.fineract.portfolio.tax.service.TaxReadPlatformService; import org.joda.time.MonthDay; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; @@ -64,12 +66,14 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService private final FineractEntityAccessUtil fineractEntityAccessUtil; private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + private final TaxReadPlatformService taxReadPlatformService; @Autowired public ChargeReadPlatformServiceImpl(final CurrencyReadPlatformService currencyReadPlatformService, final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource, final DropdownReadPlatformService dropdownReadPlatformService, final FineractEntityAccessUtil fineractEntityAccessUtil, - final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService) { + final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService, + final TaxReadPlatformService taxReadPlatformService) { this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService; this.jdbcTemplate = new JdbcTemplate(dataSource); this.currencyReadPlatformService = currencyReadPlatformService; @@ -77,6 +81,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService this.fineractEntityAccessUtil = fineractEntityAccessUtil; this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService; this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + this.taxReadPlatformService = taxReadPlatformService; } @Override @@ -142,11 +147,11 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService final List feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions(); final Map> incomeOrLiabilityAccountOptions = this.accountingDropdownReadPlatformService .retrieveAccountMappingOptionsForCharges(); - + final Collection taxGroupOptions = this.taxReadPlatformService.retrieveTaxGroupsForLookUp(); return ChargeData.template(currencyOptions, allowedChargeCalculationTypeOptions, allowedChargeAppliesToOptions, allowedChargeTimeOptions, chargePaymentOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, - clientChargeTimeTypeOptions, feeFrequencyOptions, incomeOrLiabilityAccountOptions); + clientChargeTimeTypeOptions, feeFrequencyOptions, incomeOrLiabilityAccountOptions, taxGroupOptions); } @Override @@ -252,7 +257,8 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService // Check if branch specific products are enabled. If yes, fetch only // charges mapped to current user's office - String inClause = fineractEntityAccessUtil.getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.CHARGE); + String inClause = fineractEntityAccessUtil + .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.CHARGE); if ((inClause != null) && (!(inClause.trim().isEmpty()))) { sql += " and c.id in ( " + inClause + " ) "; } @@ -271,9 +277,11 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService + "oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " + "oc.internationalized_name_code as currencyNameCode, c.fee_on_day as feeOnDay, c.fee_on_month as feeOnMonth, " + "c.fee_interval as feeInterval, c.fee_frequency as feeFrequency,c.min_cap as minCap,c.max_cap as maxCap, " - + "c.income_or_liability_account_id as glAccountId , acc.name as glAccountName, acc.gl_code as glCode " - + "from m_charge c " + "join m_organisation_currency oc on c.currency_code = oc.code " - + " LEFT JOIN acc_gl_account acc on acc.id = c.income_or_liability_account_id "; + + "c.income_or_liability_account_id as glAccountId , acc.name as glAccountName, acc.gl_code as glCode, " + + "tg.id as taxGroupId, tg.name as taxGroupName " + "from m_charge c " + + "join m_organisation_currency oc on c.currency_code = oc.code " + + " LEFT JOIN acc_gl_account acc on acc.id = c.income_or_liability_account_id " + + " LEFT JOIN m_tax_group tg on tg.id = c.tax_group_id "; } public String loanProductChargeSchema() { @@ -339,8 +347,16 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService glAccountData = new GLAccountData(glAccountId, glAccountName, glCode); } + final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId"); + final String taxGroupName = rs.getString("taxGroupName"); + TaxGroupData taxGroupData = null; + if (taxGroupId != null) { + taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); + } + return ChargeData.instance(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType, - chargePaymentMode, feeOnMonthDay, feeInterval, penalty, active, minCap, maxCap, feeFrequencyType, glAccountData); + chargePaymentMode, feeOnMonthDay, feeInterval, penalty, active, minCap, maxCap, feeFrequencyType, glAccountData, + taxGroupData); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java index 332770134..35e542036 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java @@ -43,6 +43,8 @@ import org.apache.fineract.portfolio.charge.exception.ChargeNotFoundException; import org.apache.fineract.portfolio.charge.serialization.ChargeDefinitionCommandFromApiJsonDeserializer; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; +import org.apache.fineract.portfolio.tax.domain.TaxGroupRepositoryWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -64,12 +66,14 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP private final LoanProductRepository loanProductRepository; private final FineractEntityAccessUtil fineractEntityAccessUtil; private final GLAccountRepositoryWrapper gLAccountRepository; + private final TaxGroupRepositoryWrapper taxGroupRepository; @Autowired public ChargeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final ChargeDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer, final ChargeRepository chargeRepository, final LoanProductRepository loanProductRepository, final RoutingDataSource dataSource, - final FineractEntityAccessUtil fineractEntityAccessUtil, final GLAccountRepositoryWrapper glAccountRepository) { + final FineractEntityAccessUtil fineractEntityAccessUtil, final GLAccountRepositoryWrapper glAccountRepository, + final TaxGroupRepositoryWrapper taxGroupRepository) { this.context = context; this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.dataSource = dataSource; @@ -78,6 +82,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP this.loanProductRepository = loanProductRepository; this.fineractEntityAccessUtil = fineractEntityAccessUtil; this.gLAccountRepository = glAccountRepository; + this.taxGroupRepository = taxGroupRepository; } @Transactional @@ -96,14 +101,20 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP glAccount = this.gLAccountRepository.findOneWithNotFoundDetection(glAccountId); } - final Charge charge = Charge.fromJson(command, glAccount); + final Long taxGroupId = command.longValueOfParameterNamed(ChargesApiConstants.taxGroupIdParamName); + TaxGroup taxGroup = null; + if (taxGroupId != null) { + taxGroup = this.taxGroupRepository.findOneWithNotFoundDetection(taxGroupId); + } + + final Charge charge = Charge.fromJson(command, glAccount, taxGroup); this.chargeRepository.save(charge); // check if the office specific products are enabled. If yes, then // save this savings product against a specific office // i.e. this savings product is specific for this office. - fineractEntityAccessUtil.checkConfigurationAndAddProductResrictionsForUserOffice(FineractEntityAccessType.OFFICE_ACCESS_TO_CHARGES, - FineractEntityType.CHARGE, charge.getId()); + fineractEntityAccessUtil.checkConfigurationAndAddProductResrictionsForUserOffice( + FineractEntityAccessType.OFFICE_ACCESS_TO_CHARGES, FineractEntityType.CHARGE, charge.getId()); return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(charge.getId()).build(); } catch (final DataIntegrityViolationException dve) { @@ -126,7 +137,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP final Map changes = chargeForUpdate.update(command); this.fromApiJsonDeserializer.validateChargeTimeNCalculationType(chargeForUpdate.getChargeTimeType(), - chargeForUpdate.getChargeCalculation()); + chargeForUpdate.getChargeCalculation()); // MIFOSX-900: Check if the Charge has been active before and now is // deactivated: @@ -146,7 +157,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP if (isChargeExistWithLoans) { throw new ChargeCannotBeUpdatedException( "error.msg.charge.frequency.cannot.be.updated.it.is.used.in.loan", "This charge frequency cannot be updated, it is used in loan"); } - } + } // Has account Id been changed ? if (changes.containsKey(ChargesApiConstants.glAccountIdParamName)) { @@ -158,6 +169,15 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP chargeForUpdate.setAccount(newIncomeAccount); } + if (changes.containsKey(ChargesApiConstants.taxGroupIdParamName)) { + final Long newValue = command.longValueOfParameterNamed(ChargesApiConstants.taxGroupIdParamName); + TaxGroup taxGroup = null; + if (newValue != null) { + taxGroup = this.taxGroupRepository.findOneWithNotFoundDetection(newValue); + } + chargeForUpdate.setTaxGroup(taxGroup); + } + if (!changes.isEmpty()) { this.chargeRepository.save(chargeForUpdate); } @@ -182,10 +202,9 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP final Boolean isChargeExistWithSavings = isAnySavingsAssociateWithThisCharge(chargeId); // TODO: Change error messages around: - if (!loanProducts.isEmpty() || isChargeExistWithLoans - || isChargeExistWithSavings) { throw new ChargeCannotBeDeletedException( - "error.msg.charge.cannot.be.deleted.it.is.already.used.in.loan", - "This charge cannot be deleted, it is already used in loan"); } + if (!loanProducts.isEmpty() || isChargeExistWithLoans || isChargeExistWithSavings) { throw new ChargeCannotBeDeletedException( + "error.msg.charge.cannot.be.deleted.it.is.already.used.in.loan", + "This charge cannot be deleted, it is already used in loan"); } chargeForDelete.delete(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java index f38d87eeb..0c80ce3c5 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java @@ -41,6 +41,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData; import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; @@ -156,11 +157,12 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform final List feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions(); // this field is applicable only for client charges final Map> incomeOrLiabilityAccountOptions = null; + final Collection taxGroupOptions = null; return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeFrequencyOptions, - incomeOrLiabilityAccountOptions); + incomeOrLiabilityAccountOptions, taxGroupOptions); } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java index e2ebfa25e..7bebd27b1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java @@ -217,7 +217,8 @@ public class DepositsApiConstants { SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_CONTROL.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.SAVINGS_REFERENCE.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.FEE_INCOME_ACCOUNT_MAPPING.getValue(), - SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), chartsParamName)); + SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), chartsParamName, + SavingsApiConstants.withHoldTaxParamName, SavingsApiConstants.taxGroupIdParamName)); private static final Set PRECLOSURE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(preClosurePenalApplicableParamName, preClosurePenalInterestParamName, preClosurePenalInterestOnTypeIdParamName)); @@ -298,7 +299,7 @@ public class DepositsApiConstants { interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, chargesParamName, chartsParamName, depositAmountParamName, depositPeriodParamName, depositPeriodFrequencyIdParamName, - savingsAccounts, expectedFirstDepositOnDateParamName)); + savingsAccounts, expectedFirstDepositOnDateParamName, SavingsApiConstants.withHoldTaxParamName)); public static final Set FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = fixedDepositAccountRequestData(); public static final Set FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = fixedDepositAccountResponseData(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java index 6ff57f7db..f326b0817 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java @@ -38,7 +38,8 @@ public enum SavingsAccountTransactionType { APPROVE_TRANSFER(13, "savingsAccountTransactionType.approveTransfer"), // WITHDRAW_TRANSFER(14, "savingsAccountTransactionType.withdrawTransfer"), // REJECT_TRANSFER(15, "savingsAccountTransactionType.rejectTransfer"), WRITTEN_OFF(16, "savingsAccountTransactionType.writtenoff"), // - OVERDRAFT_INTEREST(17, "savingsAccountTransactionType.overdraftInterest"); // + OVERDRAFT_INTEREST(17, "savingsAccountTransactionType.overdraftInterest"), // + WITHHOLD_TAX(18, "savingsAccountTransactionType.withholdTax"); private final Integer value; private final String code; @@ -101,6 +102,9 @@ public enum SavingsAccountTransactionType { case 17: savingsAccountTransactionType = SavingsAccountTransactionType.OVERDRAFT_INTEREST; break; + case 18: + savingsAccountTransactionType = SavingsAccountTransactionType.WITHHOLD_TAX; + break; } return savingsAccountTransactionType; } @@ -117,6 +121,10 @@ public enum SavingsAccountTransactionType { return this.value.equals(SavingsAccountTransactionType.INTEREST_POSTING.getValue()); } + public boolean isWithHoldTax() { + return this.value.equals(SavingsAccountTransactionType.WITHHOLD_TAX.getValue()); + } + public boolean isWithdrawalFee() { return this.value.equals(SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue()); } @@ -162,7 +170,7 @@ public enum SavingsAccountTransactionType { } public boolean isDebit() { - return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isPayCharge() || isIncomeFromInterest(); + return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isPayCharge() || isIncomeFromInterest() || isWithHoldTax(); } public boolean isCredit() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java index 7648b61f6..e269a542c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java @@ -117,6 +117,8 @@ public class SavingsApiConstants { public static final String minBalanceForInterestCalculationParamName = "minBalanceForInterestCalculation"; public static final String withdrawBalanceParamName = "withdrawBalance"; public static final String onHoldFundsParamName = "onHoldFunds"; + public static final String withHoldTaxParamName = "withHoldTax"; + public static final String taxGroupIdParamName = "taxGroupId"; // transaction parameters public static final String transactionDateParamName = "transactionDate"; @@ -177,8 +179,8 @@ public class SavingsApiConstants { SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), allowOverdraftParamName, overdraftLimitParamName, - nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, - minRequiredBalanceParamName, enforceMinRequiredBalanceParamName, minBalanceForInterestCalculationParamName)); + nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, minRequiredBalanceParamName, + enforceMinRequiredBalanceParamName, minBalanceForInterestCalculationParamName, withHoldTaxParamName, taxGroupIdParamName)); /** * These parameters will match the class level parameters of @@ -193,18 +195,20 @@ public class SavingsApiConstants { withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, "currencyOptions", "interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", - nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName)); + nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, withHoldTaxParamName, + taxGroupIdParamName)); public static final Set SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName, monthDayFormatParamName, staffIdParamName, accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName, - interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, + interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, + lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, // withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, chargesParamName, allowOverdraftParamName, overdraftLimitParamName, minRequiredBalanceParamName, enforceMinRequiredBalanceParamName, - nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName)); + nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, withHoldTaxParamName)); /** * These parameters will match the class level parameters of diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java index d6aa66856..0648a13bc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java @@ -71,6 +71,8 @@ import org.apache.fineract.portfolio.savings.service.DepositProductReadPlatformS import org.apache.fineract.portfolio.savings.service.DepositsDropdownReadPlatformService; import org.apache.fineract.portfolio.savings.service.SavingsDropdownReadPlatformService; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; +import org.apache.fineract.portfolio.tax.service.TaxReadPlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -96,6 +98,7 @@ public class FixedDepositProductsApiResource { private final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService; private final DropdownReadPlatformService dropdownReadPlatformService; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; + private final TaxReadPlatformService taxReadPlatformService; @Autowired public FixedDepositProductsApiResource(final DepositProductReadPlatformService depositProductReadPlatformService, @@ -110,7 +113,7 @@ public class FixedDepositProductsApiResource { final InterestRateChartReadPlatformService interestRateChartReadPlatformService, final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService, final DropdownReadPlatformService dropdownReadPlatformService, - final PaymentTypeReadPlatformService paymentTypeReadPlatformService) { + final PaymentTypeReadPlatformService paymentTypeReadPlatformService, final TaxReadPlatformService taxReadPlatformService) { this.depositProductReadPlatformService = depositProductReadPlatformService; this.savingsDropdownReadPlatformService = savingsDropdownReadPlatformService; this.currencyReadPlatformService = currencyReadPlatformService; @@ -126,6 +129,7 @@ public class FixedDepositProductsApiResource { this.depositsDropdownReadPlatformService = depositsDropdownReadPlatformService; this.dropdownReadPlatformService = dropdownReadPlatformService; this.paymentTypeReadPlatformService = paymentTypeReadPlatformService; + this.taxReadPlatformService = taxReadPlatformService; } @POST @@ -287,6 +291,8 @@ public class FixedDepositProductsApiResource { Collection penaltyOptions = this.chargeReadPlatformService.retrieveSavingsApplicablePenalties(); penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions; + final Collection taxGroupOptions = this.taxReadPlatformService.retrieveTaxGroupsForLookUp(); + // interest rate chart template final InterestRateChartData chartTemplate = this.interestRateChartReadPlatformService.template(); @@ -296,14 +302,14 @@ public class FixedDepositProductsApiResource { interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate, - preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions); + preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions); } else { fixedDepositProductToReturn = FixedDepositProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate, - preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions); + preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions); } return fixedDepositProductToReturn; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java index 1fecc1b6b..cd0b7b711 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java @@ -71,6 +71,8 @@ import org.apache.fineract.portfolio.savings.service.DepositProductReadPlatformS import org.apache.fineract.portfolio.savings.service.DepositsDropdownReadPlatformService; import org.apache.fineract.portfolio.savings.service.SavingsDropdownReadPlatformService; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; +import org.apache.fineract.portfolio.tax.service.TaxReadPlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -96,6 +98,7 @@ public class RecurringDepositProductsApiResource { private final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService; private final DropdownReadPlatformService dropdownReadPlatformService; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; + private final TaxReadPlatformService taxReadPlatformService; @Autowired public RecurringDepositProductsApiResource(final DepositProductReadPlatformService depositProductReadPlatformService, @@ -110,7 +113,7 @@ public class RecurringDepositProductsApiResource { final InterestRateChartReadPlatformService interestRateChartReadPlatformService, final DepositsDropdownReadPlatformService depositsDropdownReadPlatformService, final DropdownReadPlatformService dropdownReadPlatformService, - final PaymentTypeReadPlatformService paymentTypeReadPlatformService) { + final PaymentTypeReadPlatformService paymentTypeReadPlatformService, final TaxReadPlatformService taxReadPlatformService) { this.depositProductReadPlatformService = depositProductReadPlatformService; this.savingsDropdownReadPlatformService = savingsDropdownReadPlatformService; this.currencyReadPlatformService = currencyReadPlatformService; @@ -126,6 +129,7 @@ public class RecurringDepositProductsApiResource { this.depositsDropdownReadPlatformService = depositsDropdownReadPlatformService; this.dropdownReadPlatformService = dropdownReadPlatformService; this.paymentTypeReadPlatformService = paymentTypeReadPlatformService; + this.taxReadPlatformService = taxReadPlatformService; } @POST @@ -287,6 +291,7 @@ public class RecurringDepositProductsApiResource { // interest rate chart template final InterestRateChartData chartTemplate = this.interestRateChartReadPlatformService.template(); + final Collection taxGroupOptions = this.taxReadPlatformService.retrieveTaxGroupsForLookUp(); RecurringDepositProductData recurringDepositProductToReturn = null; if (savingsProduct != null) { @@ -294,14 +299,14 @@ public class RecurringDepositProductsApiResource { interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate, - preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions); + preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions); } else { recurringDepositProductToReturn = RecurringDepositProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate, - preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions); + preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions); } return recurringDepositProductToReturn; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java index 74e644cd3..dadcdca53 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java @@ -209,7 +209,15 @@ public class SavingsAccountsApiResource { @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String update(@PathParam("accountId") final Long accountId, final String apiRequestBodyAsJson) { + public String update(@PathParam("accountId") final Long accountId, final String apiRequestBodyAsJson, + @QueryParam("command") final String commandParam) { + + if (is(commandParam, "updateWithHoldTax")) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson).updateWithHoldTax(accountId) + .build(); + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + return this.toApiJsonSerializer.serialize(result); + } final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSavingsAccount(accountId).withJson(apiRequestBodyAsJson) .build(); @@ -273,9 +281,9 @@ public class SavingsAccountsApiResource { if (result == null) { // - throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", - "postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer" }); + throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "reject", "withdrawnByApplicant", "approve", + "undoapproval", "activate", "calculateInterest", "postInterest", "close", "assignSavingsOfficer", + "unassignSavingsOfficer" }); } return this.toApiJsonSerializer.serialize(result); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java index 5397340e4..567d401e7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java @@ -66,6 +66,8 @@ import org.apache.fineract.portfolio.savings.data.SavingsProductData; import org.apache.fineract.portfolio.savings.service.SavingsDropdownReadPlatformService; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; import org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; +import org.apache.fineract.portfolio.tax.service.TaxReadPlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -87,6 +89,7 @@ public class SavingsProductsApiResource { private final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService; private final ChargeReadPlatformService chargeReadPlatformService; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; + private final TaxReadPlatformService taxReadPlatformService; @Autowired public SavingsProductsApiResource(final SavingsProductReadPlatformService savingProductReadPlatformService, @@ -97,7 +100,8 @@ public class SavingsProductsApiResource { final ApiRequestParameterHelper apiRequestParameterHelper, final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService, final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService, - final ChargeReadPlatformService chargeReadPlatformService, PaymentTypeReadPlatformService paymentTypeReadPlatformService) { + final ChargeReadPlatformService chargeReadPlatformService, PaymentTypeReadPlatformService paymentTypeReadPlatformService, + final TaxReadPlatformService taxReadPlatformService) { this.savingProductReadPlatformService = savingProductReadPlatformService; this.dropdownReadPlatformService = dropdownReadPlatformService; this.currencyReadPlatformService = currencyReadPlatformService; @@ -109,6 +113,7 @@ public class SavingsProductsApiResource { this.accountMappingReadPlatformService = accountMappingReadPlatformService; this.chargeReadPlatformService = chargeReadPlatformService; this.paymentTypeReadPlatformService = paymentTypeReadPlatformService; + this.taxReadPlatformService = taxReadPlatformService; } @POST @@ -255,19 +260,19 @@ public class SavingsProductsApiResource { Collection penaltyOptions = this.chargeReadPlatformService.retrieveSavingsApplicablePenalties(); penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions; - + final Collection taxGroupOptions = this.taxReadPlatformService.retrieveTaxGroupsForLookUp(); SavingsProductData savingsProductToReturn = null; if (savingsProduct != null) { savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, - accountingMappingOptions, chargeOptions, penaltyOptions); + accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions); } else { savingsProductToReturn = SavingsProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, - paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions); + paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions); } return savingsProductToReturn; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountData.java index 08f1cf24f..41880ddd4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountData.java @@ -28,6 +28,7 @@ import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.staff.data.StaffData; import org.apache.fineract.portfolio.charge.data.ChargeData; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; /** * Immutable data object representing abstract for Fixed and Recurring Deposit @@ -60,6 +61,8 @@ public class DepositAccountData { protected final boolean withdrawalFeeForTransfers; protected final EnumOptionData depositType; protected final BigDecimal minBalanceForInterestCalculation; + protected final boolean withHoldTax; + protected final TaxGroupData taxGroup; // associations protected final SavingsAccountSummaryData summary; @@ -92,7 +95,8 @@ public class DepositAccountData { final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, - final SavingsAccountSummaryData summary, final EnumOptionData depositType, final BigDecimal minBalanceForInterestCalculation) { + final SavingsAccountSummaryData summary, final EnumOptionData depositType, final BigDecimal minBalanceForInterestCalculation, + final boolean withHoldTax, final TaxGroupData taxGroup) { final Collection productOptions = null; final Collection fieldOfficerOptions = null; @@ -116,7 +120,7 @@ public class DepositAccountData { fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, accountChart, chartTemplate, depositType, - minBalanceForInterestCalculation); + minBalanceForInterestCalculation, withHoldTax, taxGroup); } public static DepositAccountData lookup(final Long id, final String accountNo, final EnumOptionData depositType) { @@ -158,6 +162,8 @@ public class DepositAccountData { final DepositAccountInterestRateChartData accountChart = null; final DepositAccountInterestRateChartData chartTemplate = null; final BigDecimal minBalanceForInterestCalculation = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; return new DepositAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, interestRate, interestCompoundingPeriodType, @@ -166,7 +172,7 @@ public class DepositAccountData { fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, accountChart, chartTemplate, depositType, - minBalanceForInterestCalculation); + minBalanceForInterestCalculation, withHoldTax, taxGroup); } protected DepositAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, @@ -186,7 +192,8 @@ public class DepositAccountData { final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection charges, final Collection chargeOptions, final DepositAccountInterestRateChartData accountChart, final DepositAccountInterestRateChartData chartTemplate, - final EnumOptionData depositType, final BigDecimal minBalanceForInterestCalculation) { + final EnumOptionData depositType, final BigDecimal minBalanceForInterestCalculation, final boolean withHoldTax, + final TaxGroupData taxGroup) { this.id = id; this.accountNo = accountNo; this.externalId = externalId; @@ -233,6 +240,8 @@ public class DepositAccountData { this.chartTemplate = chartTemplate; this.depositType = depositType; this.minBalanceForInterestCalculation = minBalanceForInterestCalculation; + this.taxGroup = taxGroup; + this.withHoldTax = withHoldTax; } private SavingsAccountChargeData getWithdrawalFee() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java index 588f7245b..50c393499 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java @@ -59,6 +59,7 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequi import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productIdParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.submittedOnDateParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -120,6 +121,7 @@ public class DepositAccountDataValidator { validatePreClosureDetailForSubmit(element, baseDataValidator); validateDepositTermDeatilForSubmit(element, baseDataValidator, DepositAccountType.FIXED_DEPOSIT); validateSavingsCharges(element, baseDataValidator); + validateWithHoldTax(element, baseDataValidator); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -140,6 +142,7 @@ public class DepositAccountDataValidator { validatePreClosureDetailForUpdate(element, baseDataValidator); validateDepositTermDeatilForUpdate(element, baseDataValidator, DepositAccountType.FIXED_DEPOSIT); // validateSavingsCharges(element, baseDataValidator); + validateWithHoldTax(element, baseDataValidator); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -161,7 +164,8 @@ public class DepositAccountDataValidator { validateDepositTermDeatilForSubmit(element, baseDataValidator, DepositAccountType.RECURRING_DEPOSIT); validateRecurringDetailForSubmit(element, baseDataValidator); validateSavingsCharges(element, baseDataValidator); - + validateWithHoldTax(element, baseDataValidator); + throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -182,6 +186,7 @@ public class DepositAccountDataValidator { validateDepositTermDeatilForUpdate(element, baseDataValidator, DepositAccountType.RECURRING_DEPOSIT); validateRecurringDetailForUpdate(element, baseDataValidator); // validateSavingsCharges(element, baseDataValidator); + validateWithHoldTax(element, baseDataValidator); throwExceptionIfValidationWarningsExist(dataValidationErrors); @@ -714,6 +719,13 @@ public class DepositAccountDataValidator { } } } + + private void validateWithHoldTax(final JsonElement element, final DataValidatorBuilder baseDataValidator){ + if (this.fromApiJsonHelper.parameterExists(withHoldTaxParamName, element)) { + final String withHoldTax = this.fromApiJsonHelper.extractStringNamed(withHoldTaxParamName, element); + baseDataValidator.reset().parameter(withHoldTaxParamName).value(withHoldTax).ignoreIfNull().validateForBooleanValue(); + } + } private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductData.java index c388df057..bf3680e43 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductData.java @@ -32,6 +32,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; /** * Immutable data object represents a partial data of Deposit Proucts. @@ -53,6 +54,8 @@ public class DepositProductData { protected final EnumOptionData lockinPeriodFrequencyType; // protected final boolean withdrawalFeeForTransfers; protected final BigDecimal minBalanceForInterestCalculation; + protected final boolean withHoldTax; + protected final TaxGroupData taxGroup; // accounting protected final EnumOptionData accountingRule; @@ -82,6 +85,7 @@ public class DepositProductData { protected final Collection chargeOptions; protected final Collection penaltyOptions; protected final InterestRateChartData chartTemplate; + protected final Collection taxGroupOptions; public static DepositProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, @@ -93,7 +97,8 @@ public class DepositProductData { final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection paymentTypeOptions, final Collection accountingRuleOptions, final Map> accountingMappingOptions, final Collection chargeOptions, - final Collection penaltyOptions, final InterestRateChartData chartTemplate) { + final Collection penaltyOptions, final InterestRateChartData chartTemplate, + final Collection taxGroupOptions) { final Long id = null; final String name = null; @@ -109,6 +114,8 @@ public class DepositProductData { final Collection feeToIncomeAccountMappings = null; final Collection penaltyToIncomeAccountMappings = null; final Collection interestRateCharts = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; return new DepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, @@ -116,7 +123,8 @@ public class DepositProductData { interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, - penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation); + penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation, withHoldTax, taxGroup, + taxGroupOptions); } public static DepositProductData withCharges(final DepositProductData existingProduct, final Collection charges) { @@ -132,12 +140,16 @@ public class DepositProductData { existingProduct.paymentTypeOptions, existingProduct.accountingRuleOptions, existingProduct.accountingMappingOptions, charges, existingProduct.chargeOptions, existingProduct.penaltyOptions, existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, existingProduct.interestRateCharts, existingProduct.chartTemplate, - existingProduct.minBalanceForInterestCalculation); + existingProduct.minBalanceForInterestCalculation, existingProduct.withHoldTax, existingProduct.taxGroup, + existingProduct.taxGroupOptions); } /** * Returns a {@link DepositProductData} that contains and exist * {@link DepositProductData} data with further template data for dropdowns. + * + * @param taxGroupOptions + * TODO */ public static DepositProductData withTemplate(final DepositProductData existingProduct, final Collection currencyOptions, final Collection interestCompoundingPeriodTypeOptions, @@ -147,7 +159,7 @@ public class DepositProductData { final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection paymentTypeOptions, final Collection accountingRuleOptions, final Map> accountingMappingOptions, final Collection chargeOptions, - final Collection penaltyOptions, final InterestRateChartData chartTemplate) { + final Collection penaltyOptions, final InterestRateChartData chartTemplate, Collection taxGroupOptions) { return new DepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, @@ -159,7 +171,8 @@ public class DepositProductData { lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, - existingProduct.interestRateCharts, chartTemplate, existingProduct.minBalanceForInterestCalculation); + existingProduct.interestRateCharts, chartTemplate, existingProduct.minBalanceForInterestCalculation, + existingProduct.withHoldTax, existingProduct.taxGroup, taxGroupOptions); } public static DepositProductData withAccountingDetails(final DepositProductData existingProduct, @@ -180,6 +193,7 @@ public class DepositProductData { final Map> accountingMappingOptions = null; final Collection chargeOptions = null; final Collection penaltyOptions = null; + final Collection taxGroupOptions = null; return new DepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, @@ -191,7 +205,7 @@ public class DepositProductData { lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, existingProduct.interestRateCharts, existingProduct.chartTemplate, - existingProduct.minBalanceForInterestCalculation); + existingProduct.minBalanceForInterestCalculation, existingProduct.withHoldTax, existingProduct.taxGroup, taxGroupOptions); } public static DepositProductData instance(final Long id, final String name, final String shortName, final String description, @@ -199,7 +213,7 @@ public class DepositProductData { final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final EnumOptionData accountingType, - final BigDecimal minBalanceForInterestCalculation) { + final BigDecimal minBalanceForInterestCalculation, boolean withHoldTax, TaxGroupData taxGroup) { final Map accountingMappings = null; final Collection paymentChannelToFundSourceMappings = null; @@ -221,6 +235,7 @@ public class DepositProductData { final Collection penaltyToIncomeAccountMappings = null; final Collection interestRateCharts = null; final InterestRateChartData chartTemplate = null; + final Collection taxGroupOptions = null; return new DepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, @@ -228,7 +243,8 @@ public class DepositProductData { interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, - penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation); + penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation, withHoldTax, taxGroup, + taxGroupOptions); } public static DepositProductData lookup(final Long id, final String name) { @@ -266,6 +282,9 @@ public class DepositProductData { final Collection penaltyToIncomeAccountMappings = null; final Collection interestRateCharts = null; final InterestRateChartData chartTemplate = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; + final Collection taxGroupOptions = null; return new DepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, @@ -273,7 +292,8 @@ public class DepositProductData { interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, - penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation); + penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation, withHoldTax, taxGroup, + taxGroupOptions); } public static DepositProductData withInterestChart(final DepositProductData existingProduct, @@ -290,7 +310,8 @@ public class DepositProductData { existingProduct.paymentTypeOptions, existingProduct.accountingRuleOptions, existingProduct.accountingMappingOptions, existingProduct.charges, existingProduct.chargeOptions, existingProduct.penaltyOptions, existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, interestRateCharts, - existingProduct.chartTemplate, existingProduct.minBalanceForInterestCalculation); + existingProduct.chartTemplate, existingProduct.minBalanceForInterestCalculation, existingProduct.withHoldTax, + existingProduct.taxGroup, existingProduct.taxGroupOptions); } protected DepositProductData(final Long id, final String name, final String shortName, final String description, @@ -311,7 +332,8 @@ public class DepositProductData { final Collection feeToIncomeAccountMappings, final Collection penaltyToIncomeAccountMappings, final Collection interestRateCharts, final InterestRateChartData chartTemplate, - final BigDecimal minBalanceForInterestCalculation) { + final BigDecimal minBalanceForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup, + final Collection taxGroupOptions) { this.id = id; this.name = name; @@ -357,6 +379,9 @@ public class DepositProductData { this.activeChart = activeChart(this.interestRateCharts); this.chartTemplate = chartTemplate; this.minBalanceForInterestCalculation = minBalanceForInterestCalculation; + this.taxGroup = taxGroup; + this.withHoldTax = withHoldTax; + this.taxGroupOptions = taxGroupOptions; } public static InterestRateChartData activeChart(Collection interestRateCharts) { 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 8b0e4261f..19b65e2e8 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 @@ -39,6 +39,7 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepo import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalApplicableParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestOnTypeIdParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName; @@ -56,6 +57,7 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequi import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import java.lang.reflect.Type; @@ -333,6 +335,8 @@ public class DepositProductDataValidator { validatePaymentChannelFundSourceMappings(fromApiJsonHelper, baseDataValidator, element); validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element); } + + validateTaxWithHoldingParams(baseDataValidator, element, true); } public void validatePreClosureDetailForCreate(JsonElement element, DataValidatorBuilder baseDataValidator) { @@ -539,6 +543,7 @@ public class DepositProductDataValidator { validatePaymentChannelFundSourceMappings(fromApiJsonHelper, baseDataValidator, element); validateChargeToIncomeAccountMappings(fromApiJsonHelper, baseDataValidator, element); + validateTaxWithHoldingParams(baseDataValidator, element, false); } public void validatePreClosureDetailForUpdate(JsonElement element, DataValidatorBuilder baseDataValidator) { @@ -778,4 +783,29 @@ public class DepositProductDataValidator { } } } + + private void validateTaxWithHoldingParams(final DataValidatorBuilder baseDataValidator, final JsonElement element, + final boolean isCreate) { + if (this.fromApiJsonHelper.parameterExists(withHoldTaxParamName, element)) { + final String withHoldTax = this.fromApiJsonHelper.extractStringNamed(withHoldTaxParamName, element); + baseDataValidator.reset().parameter(withHoldTaxParamName).value(withHoldTax).ignoreIfNull().validateForBooleanValue(); + } + Boolean withHoldTax = this.fromApiJsonHelper.extractBooleanNamed(withHoldTaxParamName, element); + if (withHoldTax == null) { + withHoldTax = false; + } + + if (this.fromApiJsonHelper.parameterExists(taxGroupIdParamName, element)) { + final Long taxGroupId = this.fromApiJsonHelper.extractLongNamed(taxGroupIdParamName, element); + baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).ignoreIfNull().longGreaterThanZero(); + if (withHoldTax) { + baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank(); + } + + } else if (withHoldTax && isCreate) { + final Long taxGroupId = null; + baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank(); + } + + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java index 13eaeab1a..a69a3c78d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java @@ -31,6 +31,7 @@ import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.joda.time.LocalDate; /** @@ -101,7 +102,8 @@ public class FixedDepositAccountData extends DepositAccountData { preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, - paymentTypeOptions, savingsAccountDatas, linkedAccount, transferInterestToSavings); + paymentTypeOptions, savingsAccountDatas, linkedAccount, transferInterestToSavings, depositAccountData.withHoldTax, + depositAccountData.taxGroup); } public static FixedDepositAccountData withInterestChart(final FixedDepositAccountData account, @@ -122,7 +124,7 @@ public class FixedDepositAccountData extends DepositAccountData { account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate, account.depositPeriod, account.depositPeriodFrequency, account.periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, account.savingsAccounts, - account.linkedAccount, account.transferInterestToSavings); + account.linkedAccount, account.transferInterestToSavings, account.withHoldTax, account.taxGroup); } public static FixedDepositAccountData associationsAndTemplate(final FixedDepositAccountData account, FixedDepositAccountData template, @@ -149,7 +151,7 @@ public class FixedDepositAccountData extends DepositAccountData { account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate, account.depositPeriod, account.depositPeriodFrequency, template.periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, template.savingsAccounts, - linkedAccount, account.transferInterestToSavings); + linkedAccount, account.transferInterestToSavings, account.withHoldTax, account.taxGroup); } public static FixedDepositAccountData withTemplateOptions(final FixedDepositAccountData account, @@ -178,7 +180,7 @@ public class FixedDepositAccountData extends DepositAccountData { account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate, account.depositPeriod, account.depositPeriodFrequency, periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, savingsAccounts, - account.linkedAccount, account.transferInterestToSavings); + account.linkedAccount, account.transferInterestToSavings, account.withHoldTax, account.taxGroup); } public static FixedDepositAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -206,6 +208,8 @@ public class FixedDepositAccountData extends DepositAccountData { final BigDecimal minBalanceForInterestCalculation = null; final SavingsAccountSummaryData summary = null; final Collection transactions = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; final Collection productOptions = null; final Collection fieldOfficerOptions = null; @@ -257,7 +261,7 @@ public class FixedDepositAccountData extends DepositAccountData { maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, linkedAccount, - transferInterestToSavings); + transferInterestToSavings, withHoldTax, taxGroup); } public static FixedDepositAccountData preClosureDetails(final Long accountId, BigDecimal maturityAmount, @@ -324,6 +328,8 @@ public class FixedDepositAccountData extends DepositAccountData { final EnumOptionData depositType = SavingsEnumerations.depositType(DepositAccountType.FIXED_DEPOSIT.getValue()); final PortfolioAccountData linkedAccount = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; return new FixedDepositAccountData(accountId, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, @@ -336,7 +342,7 @@ public class FixedDepositAccountData extends DepositAccountData { maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, linkedAccount, - transferInterestToSavings); + transferInterestToSavings, withHoldTax, taxGroup); } public static FixedDepositAccountData withClosureTemplateDetails(final FixedDepositAccountData account, @@ -359,7 +365,7 @@ public class FixedDepositAccountData extends DepositAccountData { account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate, account.depositPeriod, account.depositPeriodFrequency, account.periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, account.linkedAccount, - account.transferInterestToSavings); + account.transferInterestToSavings, account.withHoldTax, account.taxGroup); } @@ -389,7 +395,8 @@ public class FixedDepositAccountData extends DepositAccountData { final Collection periodFrequencyTypeOptions, final EnumOptionData depositType, final EnumOptionData onAccountClosure, final Collection onAccountClosureOptions, final Collection paymentTypeOptions, final Collection savingsAccountDatas, - final PortfolioAccountData linkedAccount, final Boolean transferInterestToSavings) { + final PortfolioAccountData linkedAccount, final Boolean transferInterestToSavings, final boolean withHoldTax, + final TaxGroupData taxGroup) { super(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldofficerId, fieldofficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType, @@ -397,7 +404,7 @@ public class FixedDepositAccountData extends DepositAccountData { lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, - chargeOptions, accountChart, chartTemplate, depositType, minBalanceForInterestCalculation); + chargeOptions, accountChart, chartTemplate, depositType, minBalanceForInterestCalculation, withHoldTax, taxGroup); this.preClosurePenalApplicable = preClosurePenalApplicable; this.preClosurePenalInterest = preClosurePenalInterest; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java index 3aec5d095..f2b2d334b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java @@ -31,6 +31,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; /** * Immutable data object representing a Fixed Deposit product. @@ -66,7 +67,7 @@ public class FixedDepositProductData extends DepositProductData { final Map> accountingMappingOptions, final Collection chargeOptions, final Collection penaltyOptions, final InterestRateChartData chartTemplate, final Collection preClosurePenalInterestOnTypeOptions, - final Collection periodFrequencyTypeOptions) { + final Collection periodFrequencyTypeOptions, final Collection taxGroupOptions) { final Long id = null; final String name = null; @@ -95,6 +96,8 @@ public class FixedDepositProductData extends DepositProductData { final BigDecimal minDepositAmount = null; final BigDecimal depositAmount = null; final BigDecimal maxDepositAmount = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; return new FixedDepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, @@ -106,7 +109,7 @@ public class FixedDepositProductData extends DepositProductData { penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount, - maxDepositAmount, periodFrequencyTypeOptions); + maxDepositAmount, periodFrequencyTypeOptions, withHoldTax, taxGroup, taxGroupOptions); } public static FixedDepositProductData withCharges(final FixedDepositProductData existingProduct, final Collection charges) { @@ -128,13 +131,17 @@ public class FixedDepositProductData extends DepositProductData { existingProduct.minDepositTerm, existingProduct.maxDepositTerm, existingProduct.minDepositTermType, existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType, existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount, - existingProduct.periodFrequencyTypeOptions); + existingProduct.periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup, + existingProduct.taxGroupOptions); } /** * Returns a {@link FixedDepositProductData} that contains and exist * {@link FixedDepositProductData} data with further template data for * dropdowns. + * + * @param taxGroupOptions + * TODO */ public static FixedDepositProductData withTemplate(final FixedDepositProductData existingProduct, final Collection currencyOptions, final Collection interestCompoundingPeriodTypeOptions, @@ -146,7 +153,7 @@ public class FixedDepositProductData extends DepositProductData { final Map> accountingMappingOptions, final Collection chargeOptions, final Collection penaltyOptions, final InterestRateChartData chartTemplate, final Collection preClosurePenalInterestOnTypeOptions, - final Collection periodFrequencyTypeOptions) { + final Collection periodFrequencyTypeOptions, final Collection taxGroupOptions) { return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, @@ -164,7 +171,8 @@ public class FixedDepositProductData extends DepositProductData { preClosurePenalInterestOnTypeOptions, existingProduct.minDepositTerm, existingProduct.maxDepositTerm, existingProduct.minDepositTermType, existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType, existingProduct.minDepositAmount, existingProduct.depositAmount, - existingProduct.maxDepositAmount, periodFrequencyTypeOptions); + existingProduct.maxDepositAmount, periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup, + taxGroupOptions); } public static FixedDepositProductData withAccountingDetails(final FixedDepositProductData existingProduct, @@ -202,7 +210,8 @@ public class FixedDepositProductData extends DepositProductData { existingProduct.minDepositTerm, existingProduct.maxDepositTerm, existingProduct.minDepositTermType, existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType, existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount, - existingProduct.periodFrequencyTypeOptions); + existingProduct.periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup, + existingProduct.taxGroupOptions); } public static FixedDepositProductData instance(final DepositProductData depositProductData, final boolean preClosurePenalApplicable, @@ -233,6 +242,7 @@ public class FixedDepositProductData extends DepositProductData { final InterestRateChartData chartTemplate = null; final Collection preClosurePenalInterestOnTypeOptions = null; final Collection periodFrequencyTypeOptions = null; + final Collection taxGroupOptions = null; return new FixedDepositProductData(depositProductData.id, depositProductData.name, depositProductData.shortName, depositProductData.description, depositProductData.currency, depositProductData.nominalAnnualInterestRate, @@ -247,7 +257,7 @@ public class FixedDepositProductData extends DepositProductData { penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount, - maxDepositAmount, periodFrequencyTypeOptions); + maxDepositAmount, periodFrequencyTypeOptions, depositProductData.withHoldTax, depositProductData.taxGroup, taxGroupOptions); } public static FixedDepositProductData lookup(final Long id, final String name) { @@ -298,6 +308,9 @@ public class FixedDepositProductData extends DepositProductData { final BigDecimal depositAmount = null; final BigDecimal maxDepositAmount = null; final Collection periodFrequencyTypeOptions = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; + final Collection taxGroupOptions = null; return new FixedDepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, @@ -309,7 +322,7 @@ public class FixedDepositProductData extends DepositProductData { penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount, - maxDepositAmount, periodFrequencyTypeOptions); + maxDepositAmount, periodFrequencyTypeOptions, withHoldTax, taxGroup, taxGroupOptions); } public static FixedDepositProductData withInterestChart(final FixedDepositProductData existingProduct, @@ -332,7 +345,8 @@ public class FixedDepositProductData extends DepositProductData { existingProduct.minDepositTerm, existingProduct.maxDepositTerm, existingProduct.minDepositTermType, existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType, existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount, - existingProduct.periodFrequencyTypeOptions); + existingProduct.periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup, + existingProduct.taxGroupOptions); } @@ -359,7 +373,8 @@ public class FixedDepositProductData extends DepositProductData { final Integer minDepositTerm, final Integer maxDepositTerm, final EnumOptionData minDepositTermType, final EnumOptionData maxDepositTermType, final Integer inMultiplesOfDepositTerm, final EnumOptionData inMultiplesOfDepositTermType, final BigDecimal minDepositAmount, final BigDecimal depositAmount, - final BigDecimal maxDepositAmount, final Collection periodFrequencyTypeOptions) { + final BigDecimal maxDepositAmount, final Collection periodFrequencyTypeOptions, final boolean withHoldTax, + final TaxGroupData taxGroup, final Collection taxGroupOptions) { super(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, @@ -367,7 +382,8 @@ public class FixedDepositProductData extends DepositProductData { interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, - penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation); + penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation, withHoldTax, taxGroup, + taxGroupOptions); // fixed deposit additional fields this.preClosurePenalApplicable = preClosurePenalApplicable; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositAccountData.java index 33856926e..6f61ef2e9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositAccountData.java @@ -30,6 +30,7 @@ import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.joda.time.LocalDate; /** @@ -115,7 +116,8 @@ public class RecurringDepositAccountData extends DepositAccountData { depositPeriod, depositPeriodFrequency, mandatoryRecommendedDepositAmount, periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments, - isCalendarInherited, recurringFrequency, recurringFrequencyType); + isCalendarInherited, recurringFrequency, recurringFrequencyType, depositAccountData.withHoldTax, + depositAccountData.taxGroup); } public static RecurringDepositAccountData withInterestChartAndRecurringDetails(final RecurringDepositAccountData account, @@ -139,7 +141,8 @@ public class RecurringDepositAccountData extends DepositAccountData { account.periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate, account.totalOverdueAmount, account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal, - account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, recurringFrequency, recurringFrequencyType); + account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, recurringFrequency, recurringFrequencyType, + account.withHoldTax, account.taxGroup); } public static RecurringDepositAccountData withTemplateOptions(final RecurringDepositAccountData account, @@ -185,7 +188,7 @@ public class RecurringDepositAccountData extends DepositAccountData { account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate, account.totalOverdueAmount, account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal, account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency, - account.recurringFrequencyType); + account.recurringFrequencyType, account.withHoldTax, account.taxGroup); } @@ -218,7 +221,7 @@ public class RecurringDepositAccountData extends DepositAccountData { account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate, account.totalOverdueAmount, account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal, account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency, - account.recurringFrequencyType); + account.recurringFrequencyType, account.withHoldTax, account.taxGroup); } public static RecurringDepositAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -296,6 +299,8 @@ public class RecurringDepositAccountData extends DepositAccountData { final boolean isCalendarInherited = false; final Integer recurringFrequency = null; final EnumOptionData recurringFrequencyType = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; return new RecurringDepositAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, @@ -309,7 +314,8 @@ public class RecurringDepositAccountData extends DepositAccountData { depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, mandatoryRecommendedDepositAmount, periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, - allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited, recurringFrequency, recurringFrequencyType); + allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited, recurringFrequency, recurringFrequencyType, + withHoldTax, taxGroup); } public static RecurringDepositAccountData preClosureDetails(final Long accountId, final BigDecimal maturityAmount, @@ -385,6 +391,8 @@ public class RecurringDepositAccountData extends DepositAccountData { final boolean isCalendarInherited = false; final Integer recurringFrequency = null; final EnumOptionData recurringFrequencyType = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; return new RecurringDepositAccountData(accountId, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, @@ -398,7 +406,8 @@ public class RecurringDepositAccountData extends DepositAccountData { depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, mandatoryRecommendedDepositAmount, periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, - allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited, recurringFrequency, recurringFrequencyType); + allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited, recurringFrequency, recurringFrequencyType, + withHoldTax, taxGroup); } public static RecurringDepositAccountData withClosureTemplateDetails(final RecurringDepositAccountData account, @@ -424,7 +433,7 @@ public class RecurringDepositAccountData extends DepositAccountData { paymentTypeOptions, savingsAccountDatas, account.expectedFirstDepositOnDate, account.totalOverdueAmount, account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal, account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency, - account.recurringFrequencyType); + account.recurringFrequencyType, account.withHoldTax, account.taxGroup); } private RecurringDepositAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, @@ -456,7 +465,8 @@ public class RecurringDepositAccountData extends DepositAccountData { final Collection savingsAccountDatas, final LocalDate expectedFirstDepositOnDate, final BigDecimal totalOverdueAmount, final Integer noOfOverdueInstallments, final boolean isMandatoryDeposit, final boolean allowWithdrawal, final boolean adjustAdvanceTowardsFuturePayments, final boolean isCalendarInherited, - final Integer recurringFrequency, final EnumOptionData recurringFrequencyType) { + final Integer recurringFrequency, final EnumOptionData recurringFrequencyType, final boolean withHoldTax, + final TaxGroupData taxGroup) { super(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldofficerId, fieldofficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType, @@ -464,7 +474,7 @@ public class RecurringDepositAccountData extends DepositAccountData { lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, - chargeOptions, accountChart, chartTemplate, depositType, minBalanceForInterestCalculation); + chargeOptions, accountChart, chartTemplate, depositType, minBalanceForInterestCalculation, withHoldTax, taxGroup); this.preClosurePenalApplicable = preClosurePenalApplicable; this.preClosurePenalInterest = preClosurePenalInterest; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositProductData.java index c17ce43ea..184a7e7d3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositProductData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/RecurringDepositProductData.java @@ -31,6 +31,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.interestratechart.data.InterestRateChartData; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; /** * Immutable data object represent a Recurring Deposit product. @@ -68,7 +69,7 @@ public class RecurringDepositProductData extends DepositProductData { final Map> accountingMappingOptions, final Collection chargeOptions, final Collection penaltyOptions, final InterestRateChartData chartTemplate, final Collection preClosurePenalInterestOnTypeOptions, - final Collection periodFrequencyTypeOptions) { + final Collection periodFrequencyTypeOptions, final Collection taxGroupOptions) { final Long id = null; final String name = null; @@ -101,6 +102,8 @@ public class RecurringDepositProductData extends DepositProductData { final BigDecimal minDepositAmount = null; final BigDecimal depositAmount = null; final BigDecimal maxDepositAmount = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; return new RecurringDepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, @@ -112,7 +115,8 @@ public class RecurringDepositProductData extends DepositProductData { penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, isMandatoryDeposit, allowWithdrawal, - adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, minDepositAmount, depositAmount, maxDepositAmount); + adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, minDepositAmount, depositAmount, maxDepositAmount, + withHoldTax, taxGroup, taxGroupOptions); } public static RecurringDepositProductData withCharges(final RecurringDepositProductData existingProduct, @@ -136,13 +140,16 @@ public class RecurringDepositProductData extends DepositProductData { existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType, existingProduct.isMandatoryDeposit, existingProduct.allowWithdrawal, existingProduct.adjustAdvanceTowardsFuturePayments, existingProduct.periodFrequencyTypeOptions, existingProduct.minDepositAmount, existingProduct.depositAmount, - existingProduct.maxDepositAmount); + existingProduct.maxDepositAmount, existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions); } /** * Returns a {@link RecurringDepositProductData} that contains and exist * {@link RecurringDepositProductData} data with further template data for * dropdowns. + * + * @param taxGroupOptions + * TODO */ public static RecurringDepositProductData withTemplate(final RecurringDepositProductData existingProduct, final Collection currencyOptions, final Collection interestCompoundingPeriodTypeOptions, @@ -154,7 +161,7 @@ public class RecurringDepositProductData extends DepositProductData { final Map> accountingMappingOptions, final Collection chargeOptions, final Collection penaltyOptions, final InterestRateChartData chartTemplate, final Collection preClosurePenalInterestOnTypeOptions, - final Collection periodFrequencyTypeOptions) { + final Collection periodFrequencyTypeOptions, final Collection taxGroupOptions) { return new RecurringDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, @@ -173,7 +180,8 @@ public class RecurringDepositProductData extends DepositProductData { existingProduct.minDepositTermType, existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType, existingProduct.isMandatoryDeposit, existingProduct.allowWithdrawal, existingProduct.adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, existingProduct.minDepositAmount, - existingProduct.depositAmount, existingProduct.maxDepositAmount); + existingProduct.depositAmount, existingProduct.maxDepositAmount, existingProduct.withHoldTax, existingProduct.taxGroup, + taxGroupOptions); } @@ -213,7 +221,7 @@ public class RecurringDepositProductData extends DepositProductData { existingProduct.maxDepositTermType, existingProduct.inMultiplesOfDepositTerm, existingProduct.inMultiplesOfDepositTermType, existingProduct.isMandatoryDeposit, existingProduct.allowWithdrawal, existingProduct.adjustAdvanceTowardsFuturePayments, existingProduct.periodFrequencyTypeOptions, existingProduct.minDepositAmount, existingProduct.depositAmount, - existingProduct.maxDepositAmount); + existingProduct.maxDepositAmount, existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions); } public static RecurringDepositProductData instance(final DepositProductData depositProductData, @@ -246,6 +254,7 @@ public class RecurringDepositProductData extends DepositProductData { final InterestRateChartData chartTemplate = null; final Collection preClosurePenalInterestOnTypeOptions = null; final Collection periodFrequencyTypeOptions = null; + final Collection taxGroupOptions = null; return new RecurringDepositProductData(depositProductData.id, depositProductData.name, depositProductData.shortName, depositProductData.description, depositProductData.currency, depositProductData.nominalAnnualInterestRate, @@ -260,7 +269,8 @@ public class RecurringDepositProductData extends DepositProductData { penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, isMandatoryDeposit, allowWithdrawal, - adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, minDepositAmount, depositAmount, maxDepositAmount); + adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, minDepositAmount, depositAmount, maxDepositAmount, + depositProductData.withHoldTax, depositProductData.taxGroup, taxGroupOptions); } public static RecurringDepositProductData lookup(final Long id, final String name) { @@ -316,6 +326,9 @@ public class RecurringDepositProductData extends DepositProductData { final BigDecimal minDepositAmount = null; final BigDecimal depositAmount = null; final BigDecimal maxDepositAmount = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; + final Collection taxGroupOptions = null; return new RecurringDepositProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, @@ -327,7 +340,8 @@ public class RecurringDepositProductData extends DepositProductData { penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, preClosurePenalInterestOnTypeOptions, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, isMandatoryDeposit, allowWithdrawal, - adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, minDepositAmount, depositAmount, maxDepositAmount); + adjustAdvanceTowardsFuturePayments, periodFrequencyTypeOptions, minDepositAmount, depositAmount, maxDepositAmount, + withHoldTax, taxGroup, taxGroupOptions); } public static RecurringDepositProductData withInterestChart(final RecurringDepositProductData product, @@ -347,7 +361,7 @@ public class RecurringDepositProductData extends DepositProductData { product.maxDepositTerm, product.minDepositTermType, product.maxDepositTermType, product.inMultiplesOfDepositTerm, product.inMultiplesOfDepositTermType, product.isMandatoryDeposit, product.allowWithdrawal, product.adjustAdvanceTowardsFuturePayments, product.periodFrequencyTypeOptions, product.minDepositAmount, - product.depositAmount, product.maxDepositAmount); + product.depositAmount, product.maxDepositAmount, product.withHoldTax, product.taxGroup, product.taxGroupOptions); } @@ -375,7 +389,8 @@ public class RecurringDepositProductData extends DepositProductData { final EnumOptionData maxDepositTermType, final Integer inMultiplesOfDepositTerm, final EnumOptionData inMultiplesOfDepositTermType, final boolean isMandatoryDeposit, boolean allowWithdrawal, boolean adjustAdvanceTowardsFuturePayments, final Collection periodFrequencyTypeOptions, - final BigDecimal minDepositAmount, final BigDecimal depositAmount, final BigDecimal maxDepositAmount) { + final BigDecimal minDepositAmount, final BigDecimal depositAmount, final BigDecimal maxDepositAmount, + final boolean withHoldTax, final TaxGroupData taxGroup, final Collection taxGroupOptions) { super(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, @@ -383,7 +398,8 @@ public class RecurringDepositProductData extends DepositProductData { interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, - penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation); + penaltyToIncomeAccountMappings, interestRateCharts, chartTemplate, minBalanceForInterestCalculation, withHoldTax, taxGroup, + taxGroupOptions); this.preClosurePenalApplicable = preClosurePenalApplicable; this.preClosurePenalInterest = preClosurePenalInterest; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java index a10ac9df1..6df8f0935 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java @@ -28,6 +28,7 @@ import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.staff.data.StaffData; import org.apache.fineract.portfolio.charge.data.ChargeData; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; /** * Immutable data object representing a savings account. @@ -64,6 +65,8 @@ public class SavingsAccountData { private final boolean enforceMinRequiredBalance; private final BigDecimal minBalanceForInterestCalculation; private final BigDecimal onHoldFunds; + private final boolean withHoldTax; + private final TaxGroupData taxGroup; // associations private final SavingsAccountSummaryData summary; @@ -87,8 +90,8 @@ public class SavingsAccountData { private final SavingsAccountChargeData withdrawalFee; @SuppressWarnings("unused") private final SavingsAccountChargeData annualFee; - private final BigDecimal nominalAnnualInterestRateOverdraft; - private final BigDecimal minOverdraftForInterestCalculation; + private final BigDecimal nominalAnnualInterestRateOverdraft; + private final BigDecimal minOverdraftForInterestCalculation; public static SavingsAccountData instance(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId, final Long groupId, final String groupName, final Long clientId, final String clientName, @@ -101,7 +104,8 @@ public class SavingsAccountData { final SavingsAccountSummaryData summary, final boolean allowOverdraft, final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal minBalanceForInterestCalculation, final BigDecimal onHoldFunds, - final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) { + final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, + final boolean withHoldTax, final TaxGroupData taxGroup) { final Collection productOptions = null; final Collection fieldOfficerOptions = null; @@ -123,8 +127,8 @@ public class SavingsAccountData { fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, - enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, + minOverdraftForInterestCalculation, withHoldTax, taxGroup); } public static SavingsAccountData lookup(final Long accountId, final String accountNo, final EnumOptionData depositType) { @@ -178,6 +182,8 @@ public class SavingsAccountData { final Collection charges = null; final Collection chargeOptions = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, @@ -186,8 +192,8 @@ public class SavingsAccountData { fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, - enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, + minOverdraftForInterestCalculation, withHoldTax, taxGroup); } public static SavingsAccountData lookupWithProductDetails(final Long accountId, final String accountNo, @@ -239,6 +245,8 @@ public class SavingsAccountData { final Collection charges = null; final Collection chargeOptions = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, @@ -247,8 +255,8 @@ public class SavingsAccountData { fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, - enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, + minOverdraftForInterestCalculation, withHoldTax, taxGroup); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template, @@ -281,7 +289,8 @@ public class SavingsAccountData { template.interestCalculationDaysInYearTypeOptions, template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions, charges, template.chargeOptions, account.allowOverdraft, account.overdraftLimit, account.minRequiredBalance, account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation, - account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation); + account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation, + account.withHoldTax, account.taxGroup); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, @@ -304,7 +313,8 @@ public class SavingsAccountData { interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, account.allowOverdraft, account.overdraftLimit, account.minRequiredBalance, account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation, - account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation); + account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation, + account.withHoldTax, account.taxGroup); } public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -343,6 +353,8 @@ public class SavingsAccountData { final boolean enforceMinRequiredBalance = false; final BigDecimal minBalanceForInterestCalculation = null; final BigDecimal onHoldFunds = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; final SavingsAccountSummaryData summary = null; final Collection transactions = null; @@ -366,8 +378,8 @@ public class SavingsAccountData { fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, - enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, + minOverdraftForInterestCalculation, withHoldTax, taxGroup); } private SavingsAccountData(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId, @@ -388,7 +400,8 @@ public class SavingsAccountData { final Collection charges, final Collection chargeOptions, final boolean allowOverdraft, final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal minBalanceForInterestCalculation, final BigDecimal onHoldFunds, - final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) { + final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, + final boolean withHoldTax, final TaxGroupData taxGroup) { this.id = id; this.accountNo = accountNo; this.depositType = depositType; @@ -446,6 +459,8 @@ public class SavingsAccountData { this.enforceMinRequiredBalance = enforceMinRequiredBalance; this.minBalanceForInterestCalculation = minBalanceForInterestCalculation; this.onHoldFunds = onHoldFunds; + this.withHoldTax = withHoldTax; + this.taxGroup = taxGroup; } private SavingsAccountChargeData getWithdrawalFee() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataValidator.java index 1f895ada6..586ef252d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataValidator.java @@ -42,6 +42,7 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalA import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productIdParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.submittedOnDateParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import java.lang.reflect.Type; @@ -205,6 +206,11 @@ public class SavingsAccountDataValidator { baseDataValidator.reset().parameter(withdrawalFeeForTransfersParamName).value(isWithdrawalFeeApplicableForTransfers) .ignoreIfNull().validateForBooleanValue(); } + + if (this.fromApiJsonHelper.parameterExists(withHoldTaxParamName, element)) { + final String withHoldTax = this.fromApiJsonHelper.extractStringNamed(withHoldTaxParamName, element); + baseDataValidator.reset().parameter(withHoldTaxParamName).value(withHoldTax).ignoreIfNull().validateForBooleanValue(); + } validateSavingsCharges(element, baseDataValidator); @@ -384,6 +390,11 @@ public class SavingsAccountDataValidator { baseDataValidator.reset().parameter(withdrawalFeeForTransfersParamName).value(isWithdrawalFeeApplicableForTransfers) .ignoreIfNull().validateForBooleanValue(); } + + if (this.fromApiJsonHelper.parameterExists(withHoldTaxParamName, element)) { + final String withHoldTax = this.fromApiJsonHelper.extractStringNamed(withHoldTaxParamName, element); + baseDataValidator.reset().parameter(withHoldTaxParamName).value(withHoldTax).ignoreIfNull().validateForBooleanValue(); + } validateOverdraftParams(baseDataValidator, element); throwExceptionIfValidationWarningsExist(dataValidationErrors); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountStatusEnumData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountStatusEnumData.java index 215a47518..7e7bd7656 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountStatusEnumData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountStatusEnumData.java @@ -46,10 +46,12 @@ public class SavingsAccountStatusEnumData { private final boolean transferInProgress; @SuppressWarnings("unused") private final boolean transferOnHold; + @SuppressWarnings("unused") + private final boolean matured; public SavingsAccountStatusEnumData(final Long id, final String code, final String value, final boolean submittedAndPendingApproval, final boolean approved, final boolean rejected, final boolean withdrawnByApplicant, final boolean active, final boolean closed, - final boolean prematureClosed, final boolean transferInProgress, final boolean transferOnHold) { + final boolean prematureClosed, final boolean transferInProgress, final boolean transferOnHold, final boolean matured) { this.id = id; this.code = code; this.value = value; @@ -62,6 +64,7 @@ public class SavingsAccountStatusEnumData { this.prematureClosed = prematureClosed; this.transferInProgress = transferInProgress; this.transferOnHold = transferOnHold; + this.matured = matured; } public Long id() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java index 627169a76..e2d3d0e3f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java @@ -39,11 +39,12 @@ public class SavingsAccountSummaryData { private final BigDecimal totalFeeCharge; private final BigDecimal totalPenaltyCharge; private final BigDecimal totalOverdraftInterestDerived; + private final BigDecimal totalWithholdTax; public SavingsAccountSummaryData(final CurrencyData currency, final BigDecimal totalDeposits, final BigDecimal totalWithdrawals, final BigDecimal totalWithdrawalFees, final BigDecimal totalAnnualFees, final BigDecimal totalInterestEarned, final BigDecimal totalInterestPosted, final BigDecimal accountBalance, final BigDecimal totalFeeCharge, - final BigDecimal totalPenaltyCharge, final BigDecimal totalOverdraftInterestDerived) { + final BigDecimal totalPenaltyCharge, final BigDecimal totalOverdraftInterestDerived,final BigDecimal totalWithholdTax) { this.currency = currency; this.totalDeposits = totalDeposits; this.totalWithdrawals = totalWithdrawals; @@ -55,5 +56,6 @@ public class SavingsAccountSummaryData { this.totalFeeCharge = totalFeeCharge; this.totalPenaltyCharge = totalPenaltyCharge; this.totalOverdraftInterestDerived = totalOverdraftInterestDerived; + this.totalWithholdTax = totalWithholdTax; } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java index 014bc324c..d4b9cf1ba 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java @@ -41,6 +41,7 @@ public class SavingsAccountTransactionEnumData { private final boolean overdraftInterest; private final boolean writtenoff; private final boolean overdraftFee = true; + private final boolean withholdTax; public SavingsAccountTransactionEnumData(final Long id, final String code, final String value) { this.id = id; @@ -58,6 +59,7 @@ public class SavingsAccountTransactionEnumData { this.rejectTransfer = Long.valueOf(SavingsAccountTransactionType.REJECT_TRANSFER.getValue()).equals(this.id); this.writtenoff = Long.valueOf(SavingsAccountTransactionType.WRITTEN_OFF.getValue()).equals(this.id); this.overdraftInterest = Long.valueOf(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()).equals(this.id); + this.withholdTax = Long.valueOf(SavingsAccountTransactionType.WITHHOLD_TAX.getValue()).equals(this.id); // this.overdraftFee = // Long.valueOf(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()).equals(this.id); } @@ -122,4 +124,8 @@ public class SavingsAccountTransactionEnumData { return this.overdraftFee; } + public boolean isWithholdTax() { + return this.withholdTax; + } + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java index 94c6ed6ea..6741164ff 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java @@ -31,6 +31,7 @@ import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; /** * Immutable data object represent a savings product. @@ -56,6 +57,10 @@ public class SavingsProductData { private final BigDecimal minRequiredBalance; private final boolean enforceMinRequiredBalance; private final BigDecimal minBalanceForInterestCalculation; + private final BigDecimal nominalAnnualInterestRateOverdraft; + private final BigDecimal minOverdraftForInterestCalculation; + private final boolean withHoldTax; + private final TaxGroupData taxGroup; // accounting private final EnumOptionData accountingRule; @@ -80,8 +85,7 @@ public class SavingsProductData { private final Map> accountingMappingOptions; private final Collection chargeOptions; private final Collection penaltyOptions; - private final BigDecimal nominalAnnualInterestRateOverdraft; - private final BigDecimal minOverdraftForInterestCalculation; + private final Collection taxGroupOptions; public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, @@ -93,7 +97,7 @@ public class SavingsProductData { final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection paymentTypeOptions, final Collection accountingRuleOptions, final Map> accountingMappingOptions, final Collection chargeOptions, - final Collection penaltyOptions) { + final Collection penaltyOptions, final Collection taxGroupOptions) { final Long id = null; final String name = null; @@ -116,7 +120,8 @@ public class SavingsProductData { final BigDecimal minBalanceForInterestCalculation = null; final BigDecimal nominalAnnualInterestRateOverdraft = null; final BigDecimal minOverdraftForInterestCalculation = null; - + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -126,7 +131,8 @@ public class SavingsProductData { lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, - minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, + taxGroup, taxGroupOptions); } public static SavingsProductData withCharges(final SavingsProductData product, final Collection charges) { @@ -141,12 +147,16 @@ public class SavingsProductData { product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions, product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings, product.allowOverdraft, product.overdraftLimit, product.minRequiredBalance, product.enforceMinRequiredBalance, product.minBalanceForInterestCalculation, - product.nominalAnnualInterestRateOverdraft, product.minOverdraftForInterestCalculation); + product.nominalAnnualInterestRateOverdraft, product.minOverdraftForInterestCalculation, product.withHoldTax, + product.taxGroup, product.taxGroupOptions); } /** * Returns a {@link SavingsProductData} that contains and exist * {@link SavingsProductData} data with further template data for dropdowns. + * + * @param taxGroupOptions + * TODO */ public static SavingsProductData withTemplate(final SavingsProductData existingProduct, final Collection currencyOptions, final Collection interestCompoundingPeriodTypeOptions, @@ -156,7 +166,7 @@ public class SavingsProductData { final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection paymentTypeOptions, final Collection accountingRuleOptions, final Map> accountingMappingOptions, final Collection chargeOptions, - final Collection penaltyOptions) { + final Collection penaltyOptions, Collection taxGroupOptions) { return new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, @@ -171,7 +181,7 @@ public class SavingsProductData { existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, existingProduct.allowOverdraft, existingProduct.overdraftLimit, existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance, existingProduct.minBalanceForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft, - existingProduct.minOverdraftForInterestCalculation); + existingProduct.minOverdraftForInterestCalculation, existingProduct.withHoldTax, existingProduct.taxGroup, taxGroupOptions); } public static SavingsProductData withAccountingDetails(final SavingsProductData existingProduct, @@ -206,7 +216,8 @@ public class SavingsProductData { penaltyToIncomeAccountMappings, existingProduct.allowOverdraft, existingProduct.overdraftLimit, existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance, existingProduct.minBalanceForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft, - existingProduct.minOverdraftForInterestCalculation); + existingProduct.minOverdraftForInterestCalculation, existingProduct.withHoldTax, existingProduct.taxGroup, + existingProduct.taxGroupOptions); } public static SavingsProductData instance(final Long id, final String name, final String shortName, final String description, @@ -215,8 +226,9 @@ public class SavingsProductData { final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, final EnumOptionData accountingType, final boolean allowOverdraft, final BigDecimal overdraftLimit, - final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal minBalanceForInterestCalculation, - final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) { + final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, + final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, + final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup) { final Map accountingMappings = null; final Collection paymentChannelToFundSourceMappings = null; @@ -236,6 +248,7 @@ public class SavingsProductData { final Collection charges = null; final Collection feeToIncomeAccountMappings = null; final Collection penaltyToIncomeAccountMappings = null; + final Collection taxGroupOptions = null; return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -245,7 +258,8 @@ public class SavingsProductData { lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, - minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, + taxGroup, taxGroupOptions); } public static SavingsProductData lookup(final Long id, final String name) { @@ -272,6 +286,8 @@ public class SavingsProductData { final BigDecimal minRequiredBalance = null; final boolean enforceMinRequiredBalance = false; final BigDecimal minBalanceForInterestCalculation = null; + final boolean withHoldTax = false; + final TaxGroupData taxGroup = null; final Collection currencyOptions = null; final Collection interestCompoundingPeriodTypeOptions = null; @@ -288,6 +304,7 @@ public class SavingsProductData { final Collection penaltyOptions = null; final Collection feeToIncomeAccountMappings = null; final Collection penaltyToIncomeAccountMappings = null; + final Collection taxGroupOptions = null; return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, @@ -297,7 +314,8 @@ public class SavingsProductData { lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, - minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, + taxGroup, taxGroupOptions); } private SavingsProductData(final Long id, final String name, final String shortName, final String description, @@ -318,8 +336,9 @@ public class SavingsProductData { final Collection feeToIncomeAccountMappings, final Collection penaltyToIncomeAccountMappings, final boolean allowOverdraft, final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, - final BigDecimal minBalanceForInterestCalculation, - final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) { + final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, + final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup, + final Collection taxGroupOptions) { this.id = id; this.name = name; this.shortName = shortName; @@ -369,6 +388,9 @@ public class SavingsProductData { this.minBalanceForInterestCalculation = minBalanceForInterestCalculation; this.nominalAnnualInterestRateOverdraft = nominalAnnualInterestRateOverdraft; this.minOverdraftForInterestCalculation = minOverdraftForInterestCalculation; + this.taxGroup = taxGroup; + this.withHoldTax = withHoldTax; + this.taxGroupOptions = taxGroupOptions; } public boolean hasAccountingEnabled() { 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 af0b1243b..912a0c0f3 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 @@ -42,6 +42,8 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalA import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -295,6 +297,8 @@ public class SavingsProductDataValidator { .ignoreIfNull().zeroOrPositiveAmount(); } + validateTaxWithHoldingParams(baseDataValidator, element, true); + throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -460,6 +464,8 @@ public class SavingsProductDataValidator { .ignoreIfNull().zeroOrPositiveAmount(); } + validateTaxWithHoldingParams(baseDataValidator, element, false); + throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -553,13 +559,42 @@ public class SavingsProductDataValidator { } if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateOverdraftParamName, element)) { - final BigDecimal nominalAnnualInterestRateOverdraft = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateOverdraftParamName, element); - baseDataValidator.reset().parameter(nominalAnnualInterestRateOverdraftParamName).value(nominalAnnualInterestRateOverdraft).ignoreIfNull().zeroOrPositiveAmount(); + final BigDecimal nominalAnnualInterestRateOverdraft = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( + nominalAnnualInterestRateOverdraftParamName, element); + baseDataValidator.reset().parameter(nominalAnnualInterestRateOverdraftParamName).value(nominalAnnualInterestRateOverdraft) + .ignoreIfNull().zeroOrPositiveAmount(); } if (this.fromApiJsonHelper.parameterExists(minOverdraftForInterestCalculationParamName, element)) { - final BigDecimal minOverdraftForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minOverdraftForInterestCalculationParamName, element); - baseDataValidator.reset().parameter(minOverdraftForInterestCalculationParamName).value(minOverdraftForInterestCalculation).ignoreIfNull().zeroOrPositiveAmount(); + final BigDecimal minOverdraftForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( + minOverdraftForInterestCalculationParamName, element); + baseDataValidator.reset().parameter(minOverdraftForInterestCalculationParamName).value(minOverdraftForInterestCalculation) + .ignoreIfNull().zeroOrPositiveAmount(); + } + + } + + private void validateTaxWithHoldingParams(final DataValidatorBuilder baseDataValidator, final JsonElement element, + final boolean isCreate) { + if (this.fromApiJsonHelper.parameterExists(withHoldTaxParamName, element)) { + final String withHoldTax = this.fromApiJsonHelper.extractStringNamed(withHoldTaxParamName, element); + baseDataValidator.reset().parameter(withHoldTaxParamName).value(withHoldTax).ignoreIfNull().validateForBooleanValue(); + } + Boolean withHoldTax = this.fromApiJsonHelper.extractBooleanNamed(withHoldTaxParamName, element); + if (withHoldTax == null) { + withHoldTax = false; + } + + if (this.fromApiJsonHelper.parameterExists(taxGroupIdParamName, element)) { + final Long taxGroupId = this.fromApiJsonHelper.extractLongNamed(taxGroupIdParamName, element); + baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).ignoreIfNull().longGreaterThanZero(); + if (withHoldTax) { + baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank(); + } + + } else if (withHoldTax && isCreate) { + final Long taxGroupId = null; + baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java index c8b7605e9..2cffa6b7a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java @@ -48,10 +48,12 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequi import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productIdParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.submittedOnDateParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.Locale; @@ -60,6 +62,7 @@ import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; +import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; @@ -166,6 +169,7 @@ public class DepositAccountAssembler { } if (product == null) { throw new SavingsProductNotFoundException(productId); } + Client client = null; Group group = null; @@ -286,6 +290,14 @@ public class DepositAccountAssembler { if (productChart != null) { accountChart = DepositAccountInterestRateChart.from(productChart); } + + boolean withHoldTax = product.withHoldTax(); + if (command.parameterExists(withHoldTaxParamName)) { + withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); + if(withHoldTax && product.getTaxGroup() == null){ + throw new UnsupportedParameterException(Arrays.asList(withHoldTaxParamName)); + } + } SavingsAccount account = null; if (depositAccountType.isFixedDeposit()) { @@ -297,7 +309,7 @@ public class DepositAccountAssembler { accountNo, externalId, accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, charges, - accountTermAndPreClosure, accountChart); + accountTermAndPreClosure, accountChart, withHoldTax); accountTermAndPreClosure.updateAccountReference(fdAccount); fdAccount.validateDomainRules(); account = fdAccount; @@ -315,7 +327,7 @@ public class DepositAccountAssembler { fieldOfficer, accountNo, externalId, accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, - charges, accountTermAndPreClosure, accountRecurringDetail, accountChart); + charges, accountTermAndPreClosure, accountRecurringDetail, accountChart, withHoldTax); accountTermAndPreClosure.updateAccountReference(rdAccount); accountRecurringDetail.updateAccountReference(rdAccount); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java index 0f2a1fa3a..2bd27c2f4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java @@ -25,6 +25,7 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transfe import java.math.BigDecimal; import java.math.MathContext; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -164,6 +165,9 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi final boolean isPreMatureClosure = false; final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); + /*** + * Update account transactionIds for post journal entries. + */ updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); /* * <<<<<<< HEAD final SavingsAccountTransactionDTO transactionDTO = new @@ -207,16 +211,13 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account, isAccountTransfer, isExceptionForBalanceCheck); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); + updateAlreadyPostedTransactions(existingTransactionIds, account); } else { final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(), paymentDetail, false, isRegularTransaction); savingsTransactionId = withdrawal.getId(); } - /*** - * Update account transactionIds for post journal entries. - */ - updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); account.close(user, command, tenantsTodayDate, changes); this.savingsAccountRepository.save(account); @@ -239,6 +240,9 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi final boolean isPreMatureClosure = false; final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); + /*** + * Update account transactionIds for post journal entries. + */ updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final MathContext mc = MathContext.DECIMAL64; @@ -282,16 +286,13 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account, isRegularTransaction, isExceptionForBalanceCheck); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); + updateAlreadyPostedTransactions(existingTransactionIds, account); } else { final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(), paymentDetail, false, isRegularTransaction); savingsTransactionId = withdrawal.getId(); } - /*** - * Update account transactionIds for post journal entries. - */ - updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); account.close(user, command, tenantsTodayDate, changes); this.savingsAccountRepository.save(account); @@ -348,12 +349,17 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi final boolean isPreMatureClosure = true; final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); + /*** + * Update account transactionIds for post journal entries. + */ updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); Long savingsTransactionId = null; + + // post interest account.postPreMaturityInterest(closedDate, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); @@ -372,16 +378,14 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account, isRegularTransaction, isExceptionForBalanceCheck); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); + updateAlreadyPostedTransactions(existingTransactionIds, account); } else { final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(), paymentDetail, false, isRegularTransaction); savingsTransactionId = withdrawal.getId(); } - /*** - * Update account transactionIds for post journal entries. - */ - updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); + account.prematureClosure(user, command, tenantsTodayDate, changes); this.savingsAccountRepository.save(account); @@ -404,6 +408,9 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi boolean isRegularTransaction = false; final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); + /*** + * Update account transactionIds for post journal entries. + */ updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); @@ -428,16 +435,13 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account, isRegularTransaction, isExceptionForBalanceCheck); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); + updateAlreadyPostedTransactions(existingTransactionIds, account); } else { final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(), paymentDetail, false, isRegularTransaction); savingsTransactionId = withdrawal.getId(); } - /*** - * Update account transactionIds for post journal entries. - */ - updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); account.prematureClosure(user, command, tenantsTodayDate, changes); this.savingsAccountRepository.save(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); @@ -460,4 +464,17 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); } + + private void updateAlreadyPostedTransactions(final Set existingTransactionIds, final SavingsAccount savingsAccount) { + List transactions = savingsAccount.getTransactions(); + int size = transactions.size(); + for (int i = size - 1;; i--) { + SavingsAccountTransaction transaction = transactions.get(i); + if (transaction.isWithdrawal() || transaction.isWithdrawalFee()) { + existingTransactionIds.add(transaction.getId()); + } else { + break; + } + } + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java index dcd446c00..3506f25f0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java @@ -36,6 +36,7 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepo import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalApplicableParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestOnTypeIdParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName; @@ -52,6 +53,7 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalan import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName; import java.math.BigDecimal; import java.util.ArrayList; @@ -77,6 +79,8 @@ import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYea import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; +import org.apache.fineract.portfolio.tax.domain.TaxGroupRepositoryWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -88,11 +92,14 @@ public class DepositProductAssembler { private final ChargeRepositoryWrapper chargeRepository; private final InterestRateChartAssembler chartAssembler; + private final TaxGroupRepositoryWrapper taxGroupRepository; @Autowired - public DepositProductAssembler(final ChargeRepositoryWrapper chargeRepository, final InterestRateChartAssembler chartAssembler) { + public DepositProductAssembler(final ChargeRepositoryWrapper chargeRepository, final InterestRateChartAssembler chartAssembler, + final TaxGroupRepositoryWrapper taxGroupRepository) { this.chargeRepository = chargeRepository; this.chartAssembler = chartAssembler; + this.taxGroupRepository = taxGroupRepository; } public FixedDepositProduct assembleFixedDepositProduct(final JsonCommand command) { @@ -162,10 +169,18 @@ public class DepositProductAssembler { if (interestRate == null) { interestRate = BigDecimal.ZERO; } + boolean withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); + + final Long taxGroupId = command.longValueOfParameterNamed(taxGroupIdParamName); + TaxGroup taxGroup = null; + if (taxGroupId != null) { + taxGroup = this.taxGroupRepository.findOneWithNotFoundDetection(taxGroupId); + } + FixedDepositProduct fixedDepositProduct = FixedDepositProduct.createNew(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, lockinPeriodFrequencyType, accountingRuleType, charges, productTermAndPreClosure, charts, - minBalanceForInterestCalculation); + minBalanceForInterestCalculation, withHoldTax, taxGroup); // update product reference productTermAndPreClosure.updateProductReference(fixedDepositProduct); @@ -250,10 +265,13 @@ public class DepositProductAssembler { interestRate = BigDecimal.ZERO; } + final boolean withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); + final TaxGroup taxGroup = assembleTaxGroup(command); + RecurringDepositProduct recurringDepositProduct = RecurringDepositProduct.createNew(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, lockinPeriodFrequencyType, accountingRuleType, charges, - productTermAndPreClosure, productRecurringDetail, charts, minBalanceForInterestCalculation); + productTermAndPreClosure, productRecurringDetail, charts, minBalanceForInterestCalculation, taxGroup, withHoldTax); // update product reference productTermAndPreClosure.updateProductReference(recurringDepositProduct); @@ -491,4 +509,13 @@ public class DepositProductAssembler { return depositRecurringDetail; } + + public TaxGroup assembleTaxGroup(final JsonCommand command) { + final Long taxGroupId = command.longValueOfParameterNamed(taxGroupIdParamName); + TaxGroup taxGroup = null; + if (taxGroupId != null) { + taxGroup = this.taxGroupRepository.findOneWithNotFoundDetection(taxGroupId); + } + return taxGroup; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java index f71383651..1cbc7255b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java @@ -94,7 +94,7 @@ public class FixedDepositAccount extends SavingsAccount { final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountInterestRateChart chart) { + final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountInterestRateChart chart, boolean withHoldTax) { final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; final boolean allowOverdraft = false; @@ -103,7 +103,7 @@ public class FixedDepositAccount extends SavingsAccount { accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, chart, - allowOverdraft, overdraftLimit); + allowOverdraft, overdraftLimit, withHoldTax); return account; } @@ -117,12 +117,12 @@ public class FixedDepositAccount extends SavingsAccount { final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, final DepositAccountTermAndPreClosure accountTermAndPreClosure, DepositAccountInterestRateChart chart, - final boolean allowOverdraft, final BigDecimal overdraftLimit) { + final boolean allowOverdraft, final BigDecimal overdraftLimit, boolean withHoldTax) { super(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit); + withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, withHoldTax); this.accountTermAndPreClosure = accountTermAndPreClosure; this.chart = chart; @@ -525,7 +525,7 @@ public class FixedDepositAccount extends SavingsAccount { } } } - + recalucateDailyBalanceDetails = applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails); if (recalucateDailyBalanceDetails) { // update existing transactions so derived balance fields are // correct. @@ -555,6 +555,8 @@ public class FixedDepositAccount extends SavingsAccount { recalucateDailyBalance = true; } + recalucateDailyBalance = applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance); + if (recalucateDailyBalance) { // update existing transactions so derived balance fields are // correct. @@ -768,11 +770,12 @@ public class FixedDepositAccount extends SavingsAccount { final Integer lockinPeriodFrequency = this.lockinPeriodFrequency; final boolean withdrawalFeeApplicableForTransfer = false; final String accountNumber = null; - final FixedDepositAccount reInvestedAccount = FixedDepositAccount - .createNewApplicationForSubmittal(client, group, product, savingsOfficer, accountNumber, externalId, accountType, - getClosedOnDate(), closedBy, interestRate, compoundingPeriodType, postingPeriodType, interestCalculationType, - daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure, newChart); + final boolean withHoldTax = this.withHoldTax; + final FixedDepositAccount reInvestedAccount = FixedDepositAccount.createNewApplicationForSubmittal(client, group, product, + savingsOfficer, accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType, + postingPeriodType, interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure, + newChart, withHoldTax); newAccountTermAndPreClosure.updateAccountReference(reInvestedAccount); newChart.updateDepositAccountReference(reInvestedAccount); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java index 9e4e07533..65fc582aa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java @@ -59,6 +59,7 @@ import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYea import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; import org.joda.time.LocalDate; import com.google.gson.JsonArray; @@ -89,7 +90,7 @@ public class FixedDepositProduct extends SavingsProduct { final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final AccountingRuleType accountingRuleType, final Set charges, final DepositProductTermAndPreClosure productTermAndPreClosure, final Set charts, - BigDecimal minBalanceForInterestCalculation) { + BigDecimal minBalanceForInterestCalculation, boolean withHoldTax, TaxGroup taxGroup) { final BigDecimal minRequiredOpeningBalance = null; final boolean withdrawalFeeApplicableForTransfer = false; @@ -99,7 +100,8 @@ public class FixedDepositProduct extends SavingsProduct { return new FixedDepositProduct(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, - productTermAndPreClosure, charts, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation); + productTermAndPreClosure, charts, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation, withHoldTax, + taxGroup); } protected FixedDepositProduct(final String name, final String shortName, final String description, final MonetaryCurrency currency, @@ -109,12 +111,13 @@ public class FixedDepositProduct extends SavingsProduct { final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set charges, final DepositProductTermAndPreClosure productTermAndPreClosure, final Set charts, - final boolean allowOverdraft, final BigDecimal overdraftLimit, final BigDecimal minBalanceForInterestCalculation) { + final boolean allowOverdraft, final BigDecimal overdraftLimit, final BigDecimal minBalanceForInterestCalculation, + boolean withHoldTax, TaxGroup taxGroup) { super(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, allowOverdraft, overdraftLimit, - minBalanceForInterestCalculation); + minBalanceForInterestCalculation, withHoldTax, taxGroup); if (charts != null) { this.charts = charts; @@ -198,7 +201,8 @@ public class FixedDepositProduct extends SavingsProduct { } } else { // assemble chart - final InterestRateChart newChart = this.chartAssembler.assembleFrom(chartElement, this.currency().getCode(), baseDataValidator); + final InterestRateChart newChart = this.chartAssembler.assembleFrom(chartElement, this.currency().getCode(), + baseDataValidator); this.addChart(newChart); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java index 1bf4e52ca..cc67fe2f0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java @@ -106,7 +106,7 @@ public class RecurringDepositAccount extends SavingsAccount { final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart) { + final DepositAccountInterestRateChart chart, final boolean withHoldTax) { final boolean allowOverdraft = false; final BigDecimal overdraftLimit = new BigDecimal(0); @@ -116,7 +116,7 @@ public class RecurringDepositAccount extends SavingsAccount { submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, - recurringDetail, chart, allowOverdraft, overdraftLimit); + recurringDetail, chart, allowOverdraft, overdraftLimit, withHoldTax); } public static RecurringDepositAccount createNewActivatedAccount(final Client client, final Group group, final SavingsProduct product, @@ -128,7 +128,7 @@ public class RecurringDepositAccount extends SavingsAccount { final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart) { + final DepositAccountInterestRateChart chart, final boolean withHoldTax) { final boolean allowOverdraft = false; final BigDecimal overdraftLimit = new BigDecimal(0); @@ -138,7 +138,7 @@ public class RecurringDepositAccount extends SavingsAccount { submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, - recurringDetail, chart, allowOverdraft, overdraftLimit); + recurringDetail, chart, allowOverdraft, overdraftLimit, withHoldTax); } private RecurringDepositAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, @@ -150,12 +150,13 @@ public class RecurringDepositAccount extends SavingsAccount { final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart, final boolean allowOverdraft, final BigDecimal overdraftLimit) { + final DepositAccountInterestRateChart chart, final boolean allowOverdraft, final BigDecimal overdraftLimit, + final boolean withHoldTax) { super(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit); + withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, withHoldTax); this.accountTermAndPreClosure = accountTermAndPreClosure; this.recurringDetail = recurringDetail; @@ -632,7 +633,7 @@ public class RecurringDepositAccount extends SavingsAccount { } } } - + applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails); if (recalucateDailyBalanceDetails) { // update existing transactions so derived balance fields are // correct. @@ -663,6 +664,8 @@ public class RecurringDepositAccount extends SavingsAccount { recalucateDailyBalance = true; } + applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance); + if (recalucateDailyBalance) { // update existing transactions so derived balance fields are // correct. @@ -1083,7 +1086,7 @@ public class RecurringDepositAccount extends SavingsAccount { accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType, postingPeriodType, interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure, - recurringDetail, newChart); + recurringDetail, newChart, withHoldTax); rdAccount.setDatesFrom(now); newAccountTermAndPreClosure.updateAccountReference(rdAccount); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositProduct.java index 0410a8bd6..e29747221 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositProduct.java @@ -47,6 +47,7 @@ import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYea import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; @Entity @DiscriminatorValue("300") @@ -67,7 +68,7 @@ public class RecurringDepositProduct extends FixedDepositProduct { final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final AccountingRuleType accountingRuleType, final Set charges, final DepositProductTermAndPreClosure productTermAndPreClosure, final DepositProductRecurringDetail recurringDetail, final Set charts, - BigDecimal minBalanceForInterestCalculation) { + BigDecimal minBalanceForInterestCalculation, final TaxGroup taxGroup, final boolean withHoldTax) { final BigDecimal minRequiredOpeningBalance = null; final boolean withdrawalFeeApplicableForTransfer = false; @@ -77,7 +78,8 @@ public class RecurringDepositProduct extends FixedDepositProduct { return new RecurringDepositProduct(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, - productTermAndPreClosure, recurringDetail, charts, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation); + productTermAndPreClosure, recurringDetail, charts, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation, + withHoldTax, taxGroup); } protected RecurringDepositProduct(final String name, final String shortName, final String description, final MonetaryCurrency currency, @@ -88,12 +90,12 @@ public class RecurringDepositProduct extends FixedDepositProduct { final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set charges, final DepositProductTermAndPreClosure productTermAndPreClosure, final DepositProductRecurringDetail recurringDetail, final Set charts, final boolean allowOverdraft, final BigDecimal overdraftLimit, - final BigDecimal minBalanceForInterestCalculation) { + final BigDecimal minBalanceForInterestCalculation, final boolean withHoldTax, final TaxGroup taxGroup) { super(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, productTermAndPreClosure, - charts, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation); + charts, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation, withHoldTax, taxGroup); this.recurringDetail = recurringDetail; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index d7493d511..053f438f5 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -29,6 +29,7 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverd import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import java.math.BigDecimal; @@ -102,6 +103,9 @@ import org.apache.fineract.portfolio.savings.exception.SavingsOfficerAssignmentD import org.apache.fineract.portfolio.savings.exception.SavingsOfficerUnassignmentDateException; import org.apache.fineract.portfolio.savings.exception.SavingsTransferTransactionsCannotBeUndoneException; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; +import org.apache.fineract.portfolio.tax.domain.TaxComponent; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; +import org.apache.fineract.portfolio.tax.service.TaxUtils; import org.apache.fineract.useradministration.domain.AppUser; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; @@ -310,6 +314,13 @@ public class SavingsAccount extends AbstractPersistable { @Column(name = "min_balance_for_interest_calculation", scale = 6, precision = 19, nullable = true) private BigDecimal minBalanceForInterestCalculation; + @Column(name = "withhold_tax", nullable = false) + protected boolean withHoldTax; + + @ManyToOne + @JoinColumn(name = "tax_group_id") + private TaxGroup taxGroup; + protected SavingsAccount() { // } @@ -324,14 +335,14 @@ public class SavingsAccount extends AbstractPersistable { final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance, final BigDecimal minRequiredBalance, final BigDecimal nominalAnnualInterestRateOverdraft, - final BigDecimal minOverdraftForInterestCalculation) { + final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax) { final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; return new SavingsAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, enforceMinRequiredBalance, - minRequiredBalance, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + minRequiredBalance, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax); } protected SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, @@ -342,11 +353,12 @@ public class SavingsAccount extends AbstractPersistable { final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, - final boolean allowOverdraft, final BigDecimal overdraftLimit) { + final boolean allowOverdraft, final BigDecimal overdraftLimit, boolean withHoldTax) { this(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, false, null, null, null); + withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, false, null, null, null, + withHoldTax); } protected SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff savingsOfficer, @@ -359,7 +371,7 @@ public class SavingsAccount extends AbstractPersistable { final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance, final BigDecimal minRequiredBalance, final BigDecimal nominalAnnualInterestRateOverdraft, - final BigDecimal minOverdraftForInterestCalculation) { + final BigDecimal minOverdraftForInterestCalculation, boolean withHoldTax) { this.client = client; this.group = group; this.product = product; @@ -404,6 +416,8 @@ public class SavingsAccount extends AbstractPersistable { this.minRequiredBalance = minRequiredBalance; this.minBalanceForInterestCalculation = product.minBalanceForInterestCalculation(); this.savingsOfficerHistory = null; + this.withHoldTax = withHoldTax; + this.taxGroup = product.getTaxGroup(); } /** @@ -458,6 +472,9 @@ public class SavingsAccount extends AbstractPersistable { Money interestPostedToDate = Money.zero(this.currency); boolean recalucateDailyBalanceDetails = false; + boolean applyWithHoldTax = isWithHoldTaxApplicableForInterestPosting(); + final List withholdTransactions = new ArrayList<>(); + withholdTransactions.addAll(findWithHoldTransactions()); for (final PostingPeriod interestPostingPeriod : postingPeriods) { @@ -479,6 +496,9 @@ public class SavingsAccount extends AbstractPersistable { interestEarnedToBePostedForPeriod.negated()); } this.transactions.add(newPostingTransaction); + if (applyWithHoldTax) { + createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate); + } recalucateDailyBalanceDetails = true; } else { boolean correctionRequired = false; @@ -488,7 +508,14 @@ public class SavingsAccount extends AbstractPersistable { correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod.negated()); } if (correctionRequired) { + boolean applyWithHoldTaxForOldTransaction = false; postingTransaction.reverse(); + final SavingsAccountTransaction withholdTransaction = findTransactionFor(interestPostingTransactionDate, + withholdTransactions); + if (withholdTransaction != null) { + withholdTransaction.reverse(); + applyWithHoldTaxForOldTransaction = true; + } SavingsAccountTransaction newPostingTransaction; if (interestEarnedToBePostedForPeriod.isGreaterThanOrEqualTo(Money.zero(currency))) { newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), @@ -498,6 +525,9 @@ public class SavingsAccount extends AbstractPersistable { interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated()); } this.transactions.add(newPostingTransaction); + if (applyWithHoldTaxForOldTransaction) { + createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate); + } recalucateDailyBalanceDetails = true; } } @@ -518,10 +548,22 @@ public class SavingsAccount extends AbstractPersistable { this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); } + protected List findWithHoldTransactions() { + final List withholdTransactions = new ArrayList<>(); + for (final SavingsAccountTransaction transaction : this.transactions) { + if ((transaction.isWithHoldTaxAndNotReversed())) { + withholdTransactions.add(transaction); + } + } + return withholdTransactions; + } + + private boolean isWithHoldTaxApplicableForInterestPosting() { + return this.withHoldTax() && this.depositAccountType().isSavingsDeposit(); + } + protected SavingsAccountTransaction findInterestPostingTransactionFor(final LocalDate postingDate) { - SavingsAccountTransaction postingTransation = null; - for (final SavingsAccountTransaction transaction : this.transactions) { if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed()) && transaction.occursOn(postingDate)) { @@ -529,10 +571,59 @@ public class SavingsAccount extends AbstractPersistable { break; } } - return postingTransation; } + protected SavingsAccountTransaction findTransactionFor(final LocalDate postingDate, final List transactions) { + SavingsAccountTransaction transaction = null; + for (final SavingsAccountTransaction savingsAccountTransaction : transactions) { + if (savingsAccountTransaction.occursOn(postingDate)) { + transaction = savingsAccountTransaction; + break; + } + } + return transaction; + } + + protected boolean createWithHoldTransaction(final BigDecimal amount, final LocalDate date) { + boolean isTaxAdded = false; + if (this.taxGroup != null && amount.compareTo(BigDecimal.ZERO) == 1) { + Map taxSplit = TaxUtils.splitTax(amount, date, this.taxGroup.getTaxGroupMappings(), amount.scale()); + BigDecimal totalTax = TaxUtils.totalTaxAmount(taxSplit); + if (totalTax.compareTo(BigDecimal.ZERO) == 1) { + SavingsAccountTransaction withholdTransaction = SavingsAccountTransaction.withHoldTax(this, office(), date, + Money.of(currency, totalTax), taxSplit); + this.transactions.add(withholdTransaction); + isTaxAdded = true; + } + } + return isTaxAdded; + } + + protected boolean updateWithHoldTransaction(final BigDecimal amount, final SavingsAccountTransaction withholdTransaction) { + boolean isTaxAdded = false; + if (this.taxGroup != null && amount.compareTo(BigDecimal.ZERO) == 1) { + Map taxSplit = TaxUtils.splitTax(amount, withholdTransaction.transactionLocalDate(), + this.taxGroup.getTaxGroupMappings(), amount.scale()); + BigDecimal totalTax = TaxUtils.totalTaxAmount(taxSplit); + if (totalTax.compareTo(BigDecimal.ZERO) == 1) { + if (withholdTransaction.getId() == null) { + withholdTransaction.updateAmount(Money.of(currency, totalTax)); + withholdTransaction.getTaxDetails().clear(); + SavingsAccountTransaction.updateTaxDetails(taxSplit, withholdTransaction); + isTaxAdded = true; + } else if (totalTax.compareTo(withholdTransaction.getAmount()) != 0) { + withholdTransaction.reverse(); + SavingsAccountTransaction newWithholdTransaction = SavingsAccountTransaction.withHoldTax(this, office(), + withholdTransaction.transactionLocalDate(), Money.of(currency, totalTax), taxSplit); + this.transactions.add(newWithholdTransaction); + isTaxAdded = true; + } + } + } + return isTaxAdded; + } + // Determine the last transaction for given day protected SavingsAccountTransaction findLastTransaction(final LocalDate date) { @@ -1231,6 +1322,15 @@ public class SavingsAccount extends AbstractPersistable { this.minRequiredBalance = newValue; } + if (command.isChangeInBooleanParameterNamed(withHoldTaxParamName, this.withHoldTax)) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); + actualChanges.put(withHoldTaxParamName, newValue); + this.withHoldTax = newValue; + if (this.withHoldTax && this.taxGroup == null) { + baseDataValidator.reset().parameter(withHoldTaxParamName).failWithCode("not.supported.for.this.account"); + } + } + validateLockinDetails(baseDataValidator); esnureOverdraftLimitsSetForOverdraftAccounts(); } @@ -2605,4 +2705,32 @@ public class SavingsAccount extends AbstractPersistable { public BigDecimal getWithdrawableBalance() { return getAccountBalance().subtract(minRequiredBalanceDerived(getCurrency()).getAmount()).subtract(this.getOnHoldFunds()); } + + public TaxGroup getTaxGroup() { + return this.taxGroup; + } + + public boolean withHoldTax() { + return this.withHoldTax; + } + + public void setWithHoldTax(boolean withHoldTax) { + this.withHoldTax = withHoldTax; + } + + protected boolean applyWithholdTaxForDepositAccounts(final LocalDate interestPostingUpToDate, boolean recalucateDailyBalance) { + final List withholdTransactions = findWithHoldTransactions(); + SavingsAccountTransaction withholdTransaction = findTransactionFor(interestPostingUpToDate, withholdTransactions); + final BigDecimal totalInterestPosted = this.savingsAccountTransactionSummaryWrapper.calculateTotalInterestPosted(this.currency, + this.transactions); + if (withholdTransaction == null && this.withHoldTax()) { + boolean isWithholdTaxAdded = createWithHoldTransaction(totalInterestPosted, interestPostingUpToDate); + recalucateDailyBalance = recalucateDailyBalance || isWithholdTaxAdded; + } else { + boolean isWithholdTaxAdded = updateWithHoldTransaction(totalInterestPosted, withholdTransaction); + recalucateDailyBalance = recalucateDailyBalance || isWithholdTaxAdded; + } + + return recalucateDailyBalance; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java index ec30ea8f9..c26bb5293 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java @@ -40,12 +40,15 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalA import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productIdParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.submittedOnDateParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import java.math.BigDecimal; +import java.util.Arrays; import java.util.Set; import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.staff.domain.Staff; @@ -240,16 +243,18 @@ public class SavingsAccountAssembler { BigDecimal nominalAnnualInterestRateOverdraft = BigDecimal.ZERO; if (command.parameterExists(nominalAnnualInterestRateOverdraftParamName)) { - nominalAnnualInterestRateOverdraft = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(nominalAnnualInterestRateOverdraftParamName); + nominalAnnualInterestRateOverdraft = command + .bigDecimalValueOfParameterNamedDefaultToNullIfZero(nominalAnnualInterestRateOverdraftParamName); } else { - nominalAnnualInterestRateOverdraft = product.nominalAnnualInterestRateOverdraft(); + nominalAnnualInterestRateOverdraft = product.nominalAnnualInterestRateOverdraft(); } BigDecimal minOverdraftForInterestCalculation = BigDecimal.ZERO; if (command.parameterExists(minOverdraftForInterestCalculationParamName)) { - minOverdraftForInterestCalculation = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minOverdraftForInterestCalculationParamName); + minOverdraftForInterestCalculation = command + .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minOverdraftForInterestCalculationParamName); } else { - minOverdraftForInterestCalculation = product.minOverdraftForInterestCalculation(); + minOverdraftForInterestCalculation = product.minOverdraftForInterestCalculation(); } boolean enforceMinRequiredBalance = false; @@ -266,12 +271,20 @@ public class SavingsAccountAssembler { minRequiredBalance = product.minRequiredBalance(); } + boolean withHoldTax = product.withHoldTax(); + if (command.parameterExists(withHoldTaxParamName)) { + withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); + if(withHoldTax && product.getTaxGroup() == null){ + throw new UnsupportedParameterException(Arrays.asList(withHoldTaxParamName)); + } + } + final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo, externalId, accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, charges, allowOverdraft, - overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, nominalAnnualInterestRateOverdraft, + minOverdraftForInterestCalculation, withHoldTax); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), SAVINGS_ACCOUNT_RESOURCE_NAME); @@ -319,14 +332,13 @@ public class SavingsAccountAssembler { } final Set charges = this.savingsAccountChargeAssembler.fromSavingsProduct(product); - final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, null, null, null, accountType, appliedonDate, appliedBy, product.nominalAnnualInterestRate(), product.interestCompoundingPeriodType(), product.interestPostingPeriodType(), product.interestCalculationType(), product.interestCalculationDaysInYearType(), product.minRequiredOpeningBalance(), product.lockinPeriodFrequency(), product.lockinPeriodFrequencyType(), product.isWithdrawalFeeApplicableForTransfer(), charges, product.isAllowOverdraft(), product.overdraftLimit(), product.isMinRequiredBalanceEnforced(), product.minRequiredBalance(), product.nominalAnnualInterestRateOverdraft(), - product.minOverdraftForInterestCalculation()); + product.minOverdraftForInterestCalculation(), product.withHoldTax()); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), SAVINGS_ACCOUNT_RESOURCE_NAME); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java index 5a93704f4..7a2b5f262 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java @@ -71,7 +71,10 @@ public final class SavingsAccountSummary { private BigDecimal totalPenaltyChargesWaived = BigDecimal.ZERO; @Column(name = "total_overdraft_interest_derived", scale = 6, precision = 19) - private BigDecimal totalOverdraftInterestDerived; + private BigDecimal totalOverdraftInterestDerived; + + @Column(name = "total_withhold_tax_derived", scale = 6, precision = 19) + private BigDecimal totalWithholdTax; protected SavingsAccountSummary() { // @@ -90,10 +93,12 @@ public final class SavingsAccountSummary { this.totalFeeChargesWaived = wrapper.calculateTotalFeesChargeWaived(currency, transactions); this.totalPenaltyChargesWaived = wrapper.calculateTotalPenaltyChargeWaived(currency, transactions); this.totalOverdraftInterestDerived = wrapper.calculateTotalOverdraftInterest(currency, transactions); + this.totalWithholdTax = wrapper.calculateTotalWithholdTaxWithdrawal(currency, transactions); + this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals) .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge) - .minus(totalOverdraftInterestDerived).getAmount(); + .minus(totalOverdraftInterestDerived).minus(totalWithholdTax).getAmount(); } public void updateFromInterestPeriodSummaries(final MonetaryCurrency currency, final List allPostingPeriods) { @@ -122,4 +127,8 @@ public final class SavingsAccountSummary { return this.accountBalance; } + public BigDecimal getTotalInterestPosted() { + return this.totalInterestPosted; + } + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java index d09c6678b..a9cfc5d3e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.savings.domain; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -47,6 +48,7 @@ import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.apache.fineract.portfolio.savings.domain.interest.EndOfDayBalance; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; +import org.apache.fineract.portfolio.tax.domain.TaxComponent; import org.apache.fineract.useradministration.domain.AppUser; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; @@ -115,6 +117,11 @@ public final class SavingsAccountTransaction extends AbstractPersistable { @JoinColumn(name = "appuser_id", nullable = true) private AppUser appUser; + @LazyCollection(LazyCollectionOption.FALSE) + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "savings_transaction_id", referencedColumnName = "id", nullable = false) + private final List taxDetails = new ArrayList<>(); + protected SavingsAccountTransaction() { this.dateOf = null; this.typeOf = null; @@ -142,14 +149,14 @@ public final class SavingsAccountTransaction extends AbstractPersistable { amount, isReversed, null); } - public static SavingsAccountTransaction overdraftInterest(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final Money amount) { + public static SavingsAccountTransaction overdraftInterest(final SavingsAccount savingsAccount, final Office office, + final LocalDate date, final Money amount) { final boolean isReversed = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue(), date, amount, isReversed, null); - } + } - public static SavingsAccountTransaction withdrawalFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, + public static SavingsAccountTransaction withdrawalFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, final Money amount, final AppUser appUser) { final boolean isReversed = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue(), date, amount, @@ -204,6 +211,23 @@ public final class SavingsAccountTransaction extends AbstractPersistable { .getAccountBalance(), isReversed, appUser); } + public static SavingsAccountTransaction withHoldTax(final SavingsAccount savingsAccount, final Office office, final LocalDate date, + final Money amount, final Map taxDetails) { + final boolean isReversed = false; + SavingsAccountTransaction accountTransaction = new SavingsAccountTransaction(savingsAccount, office, + SavingsAccountTransactionType.WITHHOLD_TAX.getValue(), date, amount, isReversed, null); + updateTaxDetails(taxDetails, accountTransaction); + return accountTransaction; + } + + public static void updateTaxDetails(final Map taxDetails, final SavingsAccountTransaction accountTransaction) { + if (taxDetails != null) { + for (Map.Entry mapEntry : taxDetails.entrySet()) { + accountTransaction.getTaxDetails().add(new SavingsAccountTransactionTaxDetails(mapEntry.getKey(), mapEntry.getValue())); + } + } + } + public static SavingsAccountTransaction copyTransaction(SavingsAccountTransaction accountTransaction) { return new SavingsAccountTransaction(accountTransaction.savingsAccount, accountTransaction.office, accountTransaction.paymentDetail, accountTransaction.typeOf, accountTransaction.transactionLocalDate(), @@ -405,6 +429,19 @@ public final class SavingsAccountTransaction extends AbstractPersistable { thisTransactionData.put("savingsChargesPaid", savingsChargesPaidData); } + if (!this.taxDetails.isEmpty()) { + final List> taxData = new ArrayList<>(); + for (final SavingsAccountTransactionTaxDetails taxDetails : this.taxDetails) { + final Map taxDetailsData = new HashMap<>(); + taxDetailsData.put("amount", taxDetails.getAmount()); + if (taxDetails.getTaxComponent().getCreditAcount() != null) { + taxDetailsData.put("creditAccountId", taxDetails.getTaxComponent().getCreditAcount().getId()); + } + taxData.add(taxDetailsData); + } + thisTransactionData.put("taxDetails", taxData); + } + return thisTransactionData; } @@ -525,18 +562,19 @@ public final class SavingsAccountTransaction extends AbstractPersistable { } public boolean isDebit() { - return isWithdrawal() - || isWithdrawalFeeAndNotReversed() - || isAnnualFeeAndNotReversed() - || isPayCharge() - || isOverdraftInterestAndNotReversed(); + return isWithdrawal() || isWithdrawalFeeAndNotReversed() || isAnnualFeeAndNotReversed() || isPayCharge() + || isOverdraftInterestAndNotReversed() || isWithHoldTaxAndNotReversed(); + } + + public boolean isWithHoldTaxAndNotReversed() { + return SavingsAccountTransactionType.fromInt(this.typeOf).isWithHoldTax() && isNotReversed(); } public boolean isOverdraftInterestAndNotReversed() { - return SavingsAccountTransactionType.fromInt(this.typeOf).isIncomeFromInterest() && isNotReversed(); + return SavingsAccountTransactionType.fromInt(this.typeOf).isIncomeFromInterest() && isNotReversed(); } - public boolean isPayCharge() { + public boolean isPayCharge() { return SavingsAccountTransactionType.fromInt(this.typeOf).isPayCharge(); } @@ -637,4 +675,12 @@ public final class SavingsAccountTransaction extends AbstractPersistable { return this.amount; } + public List getTaxDetails() { + return this.taxDetails; + } + + public void updateAmount(final Money amount) { + this.amount = amount.getAmount(); + } + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java index 0a367de96..744180d64 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java @@ -122,8 +122,7 @@ public final class SavingsAccountTransactionSummaryWrapper { return total.getAmountDefaultedToNullIfZero(); } - public BigDecimal calculateTotalOverdraftInterest(MonetaryCurrency currency, - List transactions) { + public BigDecimal calculateTotalOverdraftInterest(MonetaryCurrency currency, List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { if (transaction.isOverdraftInterestAndNotReversed()) { @@ -131,6 +130,16 @@ public final class SavingsAccountTransactionSummaryWrapper { } } return total.getAmountDefaultedToNullIfZero(); - } + } + + public BigDecimal calculateTotalWithholdTaxWithdrawal(MonetaryCurrency currency, List transactions) { + Money total = Money.zero(currency); + for (final SavingsAccountTransaction transaction : transactions) { + if (transaction.isWithHoldTaxAndNotReversed()) { + total = total.plus(transaction.getAmount(currency)); + } + } + return total.getAmountDefaultedToNullIfZero(); + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionTaxDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionTaxDetails.java new file mode 100644 index 000000000..4c13b67c6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionTaxDetails.java @@ -0,0 +1,63 @@ +/** + * 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.domain; + +import java.math.BigDecimal; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.tax.domain.TaxComponent; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_savings_account_transaction_tax_details") +public class SavingsAccountTransactionTaxDetails extends AbstractPersistable { + + @ManyToOne + @JoinColumn(name = "tax_component_id", nullable = false) + private TaxComponent taxComponent; + + @Column(name = "amount", scale = 6, precision = 19, nullable = false) + private BigDecimal amount; + + protected SavingsAccountTransactionTaxDetails() {} + + public SavingsAccountTransactionTaxDetails(final TaxComponent taxComponent, final BigDecimal amount) { + this.taxComponent = taxComponent; + this.amount = amount; + } + + public TaxComponent getTaxComponent() { + return this.taxComponent; + } + + public BigDecimal getAmount() { + return this.amount; + } + + public void updateAmount(Money amount) { + this.amount = amount.getAmount(); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java index d5aaa5d23..1b100a0bb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java @@ -44,6 +44,8 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalA import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import java.math.BigDecimal; import java.util.ArrayList; @@ -63,6 +65,7 @@ import javax.persistence.Inheritance; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; import javax.persistence.Table; import javax.persistence.UniqueConstraint; @@ -80,6 +83,7 @@ import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYea import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; import org.joda.time.LocalDate; import org.springframework.data.jpa.domain.AbstractPersistable; @@ -156,7 +160,7 @@ public class SavingsProduct extends AbstractPersistable { protected boolean withdrawalFeeApplicableForTransfer; @ManyToMany - @JoinTable(name = "m_savings_product_charge", joinColumns = @JoinColumn(name = "savings_product_id") , inverseJoinColumns = @JoinColumn(name = "charge_id") ) + @JoinTable(name = "m_savings_product_charge", joinColumns = @JoinColumn(name = "savings_product_id"), inverseJoinColumns = @JoinColumn(name = "charge_id")) protected Set charges; @Column(name = "allow_overdraft") @@ -165,12 +169,12 @@ public class SavingsProduct extends AbstractPersistable { @Column(name = "overdraft_limit", scale = 6, precision = 19, nullable = true) private BigDecimal overdraftLimit; - @Column(name = "nominal_annual_interest_rate_overdraft", scale = 6, precision = 19, nullable = true) + @Column(name = "nominal_annual_interest_rate_overdraft", scale = 6, precision = 19, nullable = true) private BigDecimal nominalAnnualInterestRateOverdraft; @Column(name = "min_overdraft_for_interest_calculation", scale = 6, precision = 19, nullable = true) private BigDecimal minOverdraftForInterestCalculation; - + @Column(name = "enforce_min_required_balance") private boolean enforceMinRequiredBalance; @@ -180,6 +184,13 @@ public class SavingsProduct extends AbstractPersistable { @Column(name = "min_balance_for_interest_calculation", scale = 6, precision = 19, nullable = true) private BigDecimal minBalanceForInterestCalculation; + @Column(name = "withhold_tax", nullable = false) + private boolean withHoldTax; + + @ManyToOne + @JoinColumn(name = "tax_group_id") + private TaxGroup taxGroup; + public static SavingsProduct createNew(final String name, final String shortName, final String description, final MonetaryCurrency currency, final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, @@ -189,13 +200,14 @@ public class SavingsProduct extends AbstractPersistable { final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set charges, final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance, final BigDecimal minRequiredBalance, final BigDecimal minBalanceForInterestCalculation, - final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) { + final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, + boolean withHoldTax, TaxGroup taxGroup) { return new SavingsProduct(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, - allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, minBalanceForInterestCalculation, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, minBalanceForInterestCalculation, + nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup); } protected SavingsProduct() { @@ -209,11 +221,12 @@ public class SavingsProduct extends AbstractPersistable { final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set charges, - final boolean allowOverdraft, final BigDecimal overdraftLimit, BigDecimal minBalanceForInterestCalculation) { + final boolean allowOverdraft, final BigDecimal overdraftLimit, BigDecimal minBalanceForInterestCalculation, + boolean withHoldTax, TaxGroup taxGroup) { this(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, allowOverdraft, overdraftLimit, - false, null, minBalanceForInterestCalculation, null, null); + false, null, minBalanceForInterestCalculation, null, null, withHoldTax, taxGroup); } protected SavingsProduct(final String name, final String shortName, final String description, final MonetaryCurrency currency, @@ -224,7 +237,8 @@ public class SavingsProduct extends AbstractPersistable { final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set charges, final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance, final BigDecimal minRequiredBalance, BigDecimal minBalanceForInterestCalculation, - final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation) { + final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, + final boolean withHoldTax, final TaxGroup taxGroup) { this.name = name; this.shortName = shortName; @@ -267,6 +281,8 @@ public class SavingsProduct extends AbstractPersistable { this.enforceMinRequiredBalance = enforceMinRequiredBalance; this.minRequiredBalance = minRequiredBalance; this.minBalanceForInterestCalculation = minBalanceForInterestCalculation; + this.withHoldTax = withHoldTax; + this.taxGroup = taxGroup; } /** @@ -276,9 +292,11 @@ public class SavingsProduct extends AbstractPersistable { private void esnureOverdraftLimitsSetForOverdraftAccounts() { if (this.allowOverdraft) { - this.overdraftLimit = this.overdraftLimit == null? BigDecimal.ZERO : this.overdraftLimit; - this.nominalAnnualInterestRateOverdraft = this.nominalAnnualInterestRateOverdraft == null? BigDecimal.ZERO : this.nominalAnnualInterestRateOverdraft; - this.minOverdraftForInterestCalculation = this.minOverdraftForInterestCalculation == null? BigDecimal.ZERO : this.minOverdraftForInterestCalculation; + this.overdraftLimit = this.overdraftLimit == null ? BigDecimal.ZERO : this.overdraftLimit; + this.nominalAnnualInterestRateOverdraft = this.nominalAnnualInterestRateOverdraft == null ? BigDecimal.ZERO + : this.nominalAnnualInterestRateOverdraft; + this.minOverdraftForInterestCalculation = this.minOverdraftForInterestCalculation == null ? BigDecimal.ZERO + : this.minOverdraftForInterestCalculation; } } @@ -463,20 +481,24 @@ public class SavingsProduct extends AbstractPersistable { this.overdraftLimit = newValue; } - if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(nominalAnnualInterestRateOverdraftParamName, this.nominalAnnualInterestRateOverdraft)) { - final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(nominalAnnualInterestRateOverdraftParamName); + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(nominalAnnualInterestRateOverdraftParamName, + this.nominalAnnualInterestRateOverdraft)) { + final BigDecimal newValue = command + .bigDecimalValueOfParameterNamedDefaultToNullIfZero(nominalAnnualInterestRateOverdraftParamName); actualChanges.put(nominalAnnualInterestRateOverdraftParamName, newValue); actualChanges.put(localeParamName, localeAsInput); this.nominalAnnualInterestRateOverdraft = newValue; } - - if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minOverdraftForInterestCalculationParamName, this.minOverdraftForInterestCalculation)) { - final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minOverdraftForInterestCalculationParamName); + + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minOverdraftForInterestCalculationParamName, + this.minOverdraftForInterestCalculation)) { + final BigDecimal newValue = command + .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minOverdraftForInterestCalculationParamName); actualChanges.put(minOverdraftForInterestCalculationParamName, newValue); actualChanges.put(localeParamName, localeAsInput); this.minOverdraftForInterestCalculation = newValue; } - + if (!this.allowOverdraft) { this.overdraftLimit = null; this.nominalAnnualInterestRateOverdraft = null; @@ -505,6 +527,21 @@ public class SavingsProduct extends AbstractPersistable { this.minBalanceForInterestCalculation = newValue; } + if (command.isChangeInBooleanParameterNamed(withHoldTaxParamName, this.withHoldTax)) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); + actualChanges.put(withHoldTaxParamName, newValue); + this.withHoldTax = newValue; + } + + if (this.withHoldTax) { + if (this.taxGroup == null || command.isChangeInLongParameterNamed(taxGroupIdParamName, this.taxGroup.getId())) { + final Long newValue = command.longValueOfParameterNamed(taxGroupIdParamName); + actualChanges.put(taxGroupIdParamName, newValue); + } + } else { + this.taxGroup = null; + } + validateLockinDetails(); esnureOverdraftLimitsSetForOverdraftAccounts(); @@ -617,11 +654,26 @@ public class SavingsProduct extends AbstractPersistable { } public BigDecimal nominalAnnualInterestRateOverdraft() { - return this.nominalAnnualInterestRateOverdraft; - } + return this.nominalAnnualInterestRateOverdraft; + } - public BigDecimal minOverdraftForInterestCalculation() { - return this.minOverdraftForInterestCalculation; - } + public BigDecimal minOverdraftForInterestCalculation() { + return this.minOverdraftForInterestCalculation; + } + + + public TaxGroup getTaxGroup() { + return this.taxGroup; + } + + + public void setTaxGroup(TaxGroup taxGroup) { + this.taxGroup = taxGroup; + } + + + public boolean withHoldTax() { + return this.withHoldTax; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java index 823a44f28..dcbfe9322 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.savings.domain; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName; @@ -41,6 +42,7 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalA import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import java.math.BigDecimal; @@ -59,6 +61,8 @@ import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYea import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; +import org.apache.fineract.portfolio.tax.domain.TaxGroupRepositoryWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -69,10 +73,12 @@ import com.google.gson.JsonObject; public class SavingsProductAssembler { private final ChargeRepositoryWrapper chargeRepository; + private final TaxGroupRepositoryWrapper taxGroupRepository; @Autowired - public SavingsProductAssembler(final ChargeRepositoryWrapper chargeRepository) { + public SavingsProductAssembler(final ChargeRepositoryWrapper chargeRepository, final TaxGroupRepositoryWrapper taxGroupRepository) { this.chargeRepository = chargeRepository; + this.taxGroupRepository = taxGroupRepository; } public SavingsProduct assemble(final JsonCommand command) { @@ -139,18 +145,18 @@ public class SavingsProductAssembler { } BigDecimal overdraftLimit = BigDecimal.ZERO; - if(command.parameterExists(overdraftLimitParamName)){ + if (command.parameterExists(overdraftLimitParamName)) { overdraftLimit = command.bigDecimalValueOfParameterNamed(overdraftLimitParamName); } BigDecimal nominalAnnualInterestRateOverdraft = BigDecimal.ZERO; - if(command.parameterExists(nominalAnnualInterestRateOverdraftParamName)){ - nominalAnnualInterestRateOverdraft = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateOverdraftParamName); + if (command.parameterExists(nominalAnnualInterestRateOverdraftParamName)) { + nominalAnnualInterestRateOverdraft = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateOverdraftParamName); } - + BigDecimal minOverdraftForInterestCalculation = BigDecimal.ZERO; - if(command.parameterExists(minOverdraftForInterestCalculationParamName)){ - minOverdraftForInterestCalculation = command.bigDecimalValueOfParameterNamed(minOverdraftForInterestCalculationParamName); + if (command.parameterExists(minOverdraftForInterestCalculationParamName)) { + minOverdraftForInterestCalculation = command.bigDecimalValueOfParameterNamed(minOverdraftForInterestCalculationParamName); } boolean enforceMinRequiredBalance = false; @@ -159,17 +165,20 @@ public class SavingsProductAssembler { } BigDecimal minRequiredBalance = BigDecimal.ZERO; - if(command.parameterExists(minRequiredBalanceParamName)){ + if (command.parameterExists(minRequiredBalanceParamName)) { minRequiredBalance = command.bigDecimalValueOfParameterNamed(minRequiredBalanceParamName); } final BigDecimal minBalanceForInterestCalculation = command .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minBalanceForInterestCalculationParamName); + boolean withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); + final TaxGroup taxGroup = assembleTaxGroup(command); + return SavingsProduct.createNew(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, accountingRuleType, charges, allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, minBalanceForInterestCalculation, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup); } public Set assembleListOfSavingsProductCharges(final JsonCommand command, final String savingsProductCurrencyCode) { @@ -205,4 +214,13 @@ public class SavingsProductAssembler { return charges; } + + public TaxGroup assembleTaxGroup(final JsonCommand command) { + final Long taxGroupId = command.longValueOfParameterNamed(taxGroupIdParamName); + TaxGroup taxGroup = null; + if (taxGroupId != null) { + taxGroup = this.taxGroupRepository.findOneWithNotFoundDetection(taxGroupId); + } + return taxGroup; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateWithHoldTaxSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateWithHoldTaxSavingsAccountCommandHandler.java new file mode 100644 index 000000000..ce0f91882 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UpdateWithHoldTaxSavingsAccountCommandHandler.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.savings.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "SAVINGSACCOUNT", action = "UPDATEWITHHOLDTAX") +public class UpdateWithHoldTaxSavingsAccountCommandHandler implements NewCommandSourceHandler { + + private final SavingsAccountWritePlatformService writePlatformService; + + @Autowired + public UpdateWithHoldTaxSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.modifyWithHoldTax(command.entityId(), command); + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java index 45de68c56..5e0dab79a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java @@ -83,6 +83,7 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; import org.apache.fineract.portfolio.savings.exception.DepositAccountNotFoundException; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; @@ -580,8 +581,12 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead selectFieldsSqlBuilder.append("sa.account_balance_derived as accountBalance, "); selectFieldsSqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge, "); selectFieldsSqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, "); + selectFieldsSqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax,"); selectFieldsSqlBuilder.append("sa.deposit_type_enum as depositTypeId, "); - selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation "); + selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation, "); + selectFieldsSqlBuilder.append("sa.withhold_tax as withHoldTax,"); + selectFieldsSqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); + this.selectFieldsSql = selectFieldsSqlBuilder.toString(); @@ -599,6 +604,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead selectTablesSqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid "); selectTablesSqlBuilder.append("left join m_appuser avbu on rbu.id = sa.activatedon_userid "); selectTablesSqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid "); + selectTablesSqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id "); this.selectTablesSql = selectTablesSqlBuilder.toString(); } @@ -722,17 +728,26 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance"); final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalFeeCharge"); final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge"); + final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax"); final BigDecimal totalOverdraftInterestDerived = null; + + final boolean withHoldTax = rs.getBoolean("withHoldTax"); + final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId"); + final String taxGroupName = rs.getString("taxGroupName"); + TaxGroupData taxGroupData = null; + if (taxGroupId != null) { + taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); + } final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge, - totalPenaltyCharge, totalOverdraftInterestDerived); + totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax); return DepositAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, depositType, - minBalanceForInterestCalculation); + minBalanceForInterestCalculation, withHoldTax, taxGroupData); } } @@ -1068,7 +1083,9 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead selectFieldsSqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); selectFieldsSqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); selectFieldsSqlBuilder.append("sa.deposit_type_enum as depositTypeId, "); - selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation "); + selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation, "); + selectFieldsSqlBuilder.append("sa.withhold_tax as withHoldTax,"); + selectFieldsSqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); this.selectFieldsSql = selectFieldsSqlBuilder.toString(); @@ -1076,6 +1093,8 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead selectTablesSqlBuilder.append("from m_savings_product sa "); selectTablesSqlBuilder.append("left join m_deposit_product_term_and_preclosure dptp on sa.id = dptp.savings_product_id "); selectTablesSqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); + selectTablesSqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id "); + this.selectTablesSql = selectTablesSqlBuilder.toString(); } @@ -1158,12 +1177,20 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead final SavingsAccountStatusEnumData status = null; final SavingsAccountSummaryData summary = null; final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault(); + + final boolean withHoldTax = rs.getBoolean("withHoldTax"); + final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId"); + final String taxGroupName = rs.getString("taxGroupName"); + TaxGroupData taxGroupData = null; + if (taxGroupId != null) { + taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); + } return DepositAccountData.instance(null, null, null, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualIterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, depositType, - minBalanceForInterestCalculation); + minBalanceForInterestCalculation, withHoldTax, taxGroupData); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java index 70a65cfc7..3d39e87b8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java @@ -36,6 +36,7 @@ import org.apache.fineract.portfolio.savings.data.DepositProductData; import org.apache.fineract.portfolio.savings.data.FixedDepositProductData; import org.apache.fineract.portfolio.savings.data.RecurringDepositProductData; import org.apache.fineract.portfolio.savings.exception.FixedDepositProductNotFoundException; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; @@ -144,7 +145,9 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead sqlBuilder.append("sp.lockin_period_frequency as lockinPeriodFrequency,"); sqlBuilder.append("sp.lockin_period_frequency_enum as lockinPeriodFrequencyType, "); sqlBuilder.append("sp.accounting_type as accountingType, "); - sqlBuilder.append("sp.min_balance_for_interest_calculation as minBalanceForInterestCalculation "); + sqlBuilder.append("sp.min_balance_for_interest_calculation as minBalanceForInterestCalculation, "); + sqlBuilder.append("sp.withhold_tax as withHoldTax,"); + sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); this.schemaSql = sqlBuilder.toString(); } @@ -197,9 +200,18 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead } final BigDecimal minBalanceForInterestCalculation = rs.getBigDecimal("minBalanceForInterestCalculation"); + final boolean withHoldTax = rs.getBoolean("withHoldTax"); + final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId"); + final String taxGroupName = rs.getString("taxGroupName"); + TaxGroupData taxGroupData = null; + if (taxGroupId != null) { + taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); + } + return DepositProductData.instance(id, name, shortName, description, currency, nominalAnnualInterestRate, compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, - lockinPeriodFrequency, lockinPeriodFrequencyType, accountingRuleType, minBalanceForInterestCalculation); + lockinPeriodFrequency, lockinPeriodFrequencyType, accountingRuleType, minBalanceForInterestCalculation, withHoldTax, + taxGroupData); } } @@ -224,6 +236,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead sqlBuilder.append("from m_savings_product sp "); sqlBuilder.append("left join m_deposit_product_term_and_preclosure dptp on sp.id=dptp.savings_product_id "); sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); + sqlBuilder.append(" left join m_tax_group tg on tg.id = sp.tax_group_id "); this.schemaSql = sqlBuilder.toString(); } @@ -292,6 +305,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead sqlBuilder.append("left join m_deposit_product_term_and_preclosure dptp on sp.id=dptp.savings_product_id "); sqlBuilder.append("left join m_deposit_product_recurring_detail dprd on sp.id=dprd.savings_product_id "); sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); + sqlBuilder.append(" left join m_tax_group tg on tg.id = sp.tax_group_id "); this.schemaSql = sqlBuilder.toString(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositProductWritePlatformServiceJpaRepositoryImpl.java index 02c7db7df..3da5ef819 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositProductWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositProductWritePlatformServiceJpaRepositoryImpl.java @@ -18,16 +18,23 @@ */ package org.apache.fineract.portfolio.savings.service; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Set; import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.charge.domain.Charge; @@ -38,6 +45,7 @@ import org.apache.fineract.portfolio.savings.domain.DepositProductAssembler; import org.apache.fineract.portfolio.savings.domain.FixedDepositProduct; import org.apache.fineract.portfolio.savings.domain.FixedDepositProductRepository; import org.apache.fineract.portfolio.savings.exception.FixedDepositProductNotFoundException; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -118,6 +126,19 @@ public class FixedDepositProductWritePlatformServiceJpaRepositoryImpl implements } } + if (changes.containsKey(taxGroupIdParamName)) { + final TaxGroup taxGroup = this.depositProductAssembler.assembleTaxGroup(command); + product.setTaxGroup(taxGroup); + if (product.withHoldTax() && product.getTaxGroup() == null) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME); + final Long taxGroupId = null; + baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank(); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + // accounting related changes final boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName); final Map accountingMappingChanges = this.accountMappingWritePlatformService 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 2ec93c648..330981620 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 @@ -18,16 +18,24 @@ */ package org.apache.fineract.portfolio.savings.service; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Set; import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.charge.domain.Charge; @@ -38,6 +46,7 @@ import org.apache.fineract.portfolio.savings.domain.DepositProductAssembler; import org.apache.fineract.portfolio.savings.domain.RecurringDepositProduct; 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; @@ -118,6 +127,19 @@ public class RecurringDepositProductWritePlatformServiceJpaRepositoryImpl implem } } + if (changes.containsKey(taxGroupIdParamName)) { + final TaxGroup taxGroup = this.depositProductAssembler.assembleTaxGroup(command); + product.setTaxGroup(taxGroup); + if (product.withHoldTax() && product.getTaxGroup() == null) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME); + final Long taxGroupId = null; + baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank(); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + // accounting related changes final boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName); final Map accountingMappingChanges = this.accountMappingWritePlatformService diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java index baa30583a..e2fa675af 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java @@ -40,6 +40,7 @@ import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService; import org.apache.fineract.portfolio.savings.data.SavingsAccountAnnualFeeData; import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.joda.time.LocalDate; import org.joda.time.MonthDay; import org.springframework.beans.factory.annotation.Autowired; @@ -159,12 +160,14 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou final List feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions(); // this field is applicable only for client charges final Map> incomeOrLiabilityAccountOptions = null; + + final Collection taxGroupOptions = null; // TODO AA : revisit for merge conflict - Not sure method signature return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeFrequencyOptions, - incomeOrLiabilityAccountOptions); + incomeOrLiabilityAccountOptions, taxGroupOptions); } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 1e76d931c..4865fa49f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -61,6 +61,7 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.apache.fineract.portfolio.savings.data.SavingsProductData; import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; @@ -269,7 +270,11 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation,"); sqlBuilder.append("sa.min_required_balance as minRequiredBalance, "); sqlBuilder.append("sa.enforce_min_required_balance as enforceMinRequiredBalance, "); - sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds "); + sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds, "); + sqlBuilder.append("sa.withhold_tax as withHoldTax,"); + sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax,"); + sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); + sqlBuilder.append("from m_savings_account sa "); sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id "); sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); @@ -282,6 +287,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid "); sqlBuilder.append("left join m_appuser avbu on avbu.id = sa.activatedon_userid "); sqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid "); + sqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id "); this.schemaSql = sqlBuilder.toString(); } @@ -400,8 +406,10 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final boolean allowOverdraft = rs.getBoolean("allowOverdraft"); final BigDecimal overdraftLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit"); - final BigDecimal nominalAnnualInterestRateOverdraft = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "nominalAnnualInterestRateOverdraft"); - final BigDecimal minOverdraftForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minOverdraftForInterestCalculation"); + final BigDecimal nominalAnnualInterestRateOverdraft = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "nominalAnnualInterestRateOverdraft"); + final BigDecimal minOverdraftForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "minOverdraftForInterestCalculation"); final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance"); final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); @@ -431,22 +439,32 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance"); final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalFeeCharge"); final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge"); - final BigDecimal totalOverdraftInterestDerived = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverdraftInterestDerived"); - + final BigDecimal totalOverdraftInterestDerived = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "totalOverdraftInterestDerived"); + final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax"); + final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minBalanceForInterestCalculation"); final BigDecimal onHoldFunds = rs.getBigDecimal("onHoldFunds"); final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge, - totalPenaltyCharge, totalOverdraftInterestDerived); + totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax); + + final boolean withHoldTax = rs.getBoolean("withHoldTax"); + final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId"); + final String taxGroupName = rs.getString("taxGroupName"); + TaxGroupData taxGroupData = null; + if (taxGroupId != null) { + taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); + } return SavingsAccountData.instance(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, - onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData); } } @@ -540,7 +558,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Collection charges = fromChargesToSavingsCharges(productCharges); final boolean feeChargesOnly = false; - final Collection chargeOptions = this.chargeReadPlatformService.retrieveSavingsProductApplicableCharges(feeChargesOnly); + final Collection chargeOptions = this.chargeReadPlatformService + .retrieveSavingsProductApplicableCharges(feeChargesOnly); Collection fieldOfficerOptions = null; @@ -598,7 +617,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Collection charges = null; final boolean feeChargesOnly = true; - final Collection chargeOptions = this.chargeReadPlatformService.retrieveSavingsProductApplicableCharges(feeChargesOnly); + final Collection chargeOptions = this.chargeReadPlatformService + .retrieveSavingsProductApplicableCharges(feeChargesOnly); template = SavingsAccountData.withTemplateOptions(template, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, @@ -842,6 +862,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sp.overdraft_limit as overdraftLimit, "); sqlBuilder.append("sp.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, "); sqlBuilder.append("sp.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, "); + sqlBuilder.append("sp.withhold_tax as withHoldTax,"); + sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, "); + // sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,"); // sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth, "); // sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay "); @@ -849,6 +872,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sp.enforce_min_required_balance as enforceMinRequiredBalance "); sqlBuilder.append("from m_savings_product sp "); sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); + sqlBuilder.append("left join m_tax_group tg on tg.id = sp.tax_group_id "); this.schemaSql = sqlBuilder.toString(); } @@ -912,8 +936,10 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final boolean allowOverdraft = rs.getBoolean("allowOverdraft"); final BigDecimal overdraftLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit"); - final BigDecimal nominalAnnualInterestRateOverdraft = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "nominalAnnualInterestRateOverdraft"); - final BigDecimal minOverdraftForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minOverdraftForInterestCalculation"); + final BigDecimal nominalAnnualInterestRateOverdraft = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "nominalAnnualInterestRateOverdraft"); + final BigDecimal minOverdraftForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, + "minOverdraftForInterestCalculation"); final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance"); final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); @@ -933,6 +959,14 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead * MonthDay(annualFeeOnMonth, annualFeeOnDay); } */ + final boolean withHoldTax = rs.getBoolean("withHoldTax"); + final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId"); + final String taxGroupName = rs.getString("taxGroupName"); + TaxGroupData taxGroupData = null; + if (taxGroupId != null) { + taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); + } + Long clientId = null; String clientName = null; if (this.client != null) { @@ -961,7 +995,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, - onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java index 434ac09b9..3fe8aa955 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -79,4 +79,6 @@ public interface SavingsAccountWritePlatformService { Set existingReversedTransactionIds); void postInterest(SavingsAccount account); + + CommandProcessingResult modifyWithHoldTax(Long savingsAccountId, JsonCommand command); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index ae75bbf17..f1b338b15 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -89,6 +89,7 @@ import org.springframework.util.CollectionUtils; import java.math.BigDecimal; import java.math.MathContext; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -102,6 +103,7 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.amountPa import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeIdParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawBalanceParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; @Service public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements SavingsAccountWritePlatformService { @@ -129,7 +131,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi private final ConfigurationDomainService configurationDomainService; private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository; - @Autowired public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final SavingsAccountRepository savingAccountRepository, @@ -141,8 +142,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, final JournalEntryWritePlatformService journalEntryWritePlatformService, final SavingsAccountDomainService savingsAccountDomainService, final NoteRepository noteRepository, - final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final HolidayRepositoryWrapper holidayRepository, - final WorkingDaysRepositoryWrapper workingDaysRepository, + final AccountTransfersReadPlatformService accountTransfersReadPlatformService, + final HolidayRepositoryWrapper holidayRepository, final WorkingDaysRepositoryWrapper workingDaysRepository, final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository, final SavingsAccountDataValidator fromApiJsonDeserializer, final SavingsAccountRepositoryWrapper savingsRepository, @@ -169,7 +170,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingsRepository = savingsRepository; this.staffRepository = staffRepository; this.configurationDomainService = configurationDomainService; - this.depositAccountOnHoldTransactionRepository= depositAccountOnHoldTransactionRepository; + this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository; } @Transactional @@ -227,10 +228,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } account.processAccountUponActivation(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user); List depositAccountOnHoldTransactions = null; - if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){ - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); } - account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),depositAccountOnHoldTransactions); + account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(), + depositAccountOnHoldTransactions); } @Transactional @@ -316,8 +319,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi AppUser user = getAppUserIfPresent(); - final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository - .findOneWithNotFoundDetection(savingsAccountChargeId, accountId); + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, accountId); final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); @@ -360,7 +363,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .build(); } - @Override public CommandProcessingResult postInterest(final Long savingsId) { @@ -385,7 +387,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); if (account.getNominalAnnualInterestRate().compareTo(BigDecimal.ZERO) > 0 - || (account.allowOverdraft() && account.getNominalAnnualInterestRateOverdraft().compareTo(BigDecimal.ZERO) > 0)) { + || (account.allowOverdraft() && account.getNominalAnnualInterestRateOverdraft().compareTo(BigDecimal.ZERO) > 0)) { final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); @@ -426,16 +428,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi if (savingsAccountTransaction == null) { throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); } if (!allowAccountTransferModification - && this.accountTransfersReadPlatformService - .isAccountTransfer(transactionId, - PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException( - "error.msg.saving.account.transfer.transaction.update.not.allowed", "Savings account transaction:" - + transactionId + " update not allowed as it involves in account transfer", - transactionId); } + && this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException( + "error.msg.saving.account.transfer.transaction.update.not.allowed", "Savings account transaction:" + transactionId + + " update not allowed as it involves in account transfer", transactionId); } - if (!account - .allowModify()) { throw new PlatformServiceUnavailableException("error.msg.saving.account.transaction.update.not.allowed", - "Savings account transaction:" + transactionId + " update not allowed for this savings type", transactionId); } + if (!account.allowModify()) { throw new PlatformServiceUnavailableException( + "error.msg.saving.account.transaction.update.not.allowed", "Savings account transaction:" + transactionId + + " update not allowed for this savings type", transactionId); } final LocalDate today = DateUtils.getLocalDateOfTenant(); final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode()); @@ -464,10 +463,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi financialYearBeginningMonth); } List depositAccountOnHoldTransactions = null; - if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){ - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); } - account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction,depositAccountOnHoldTransactions); + account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction, depositAccountOnHoldTransactions); account.activateAccountBasedOnBalance(); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); @@ -493,14 +493,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .findOneByIdAndSavingsAccountId(transactionId, savingsId); if (savingsAccountTransaction == null) { throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); } - if (!(savingsAccountTransaction.isDeposit() || savingsAccountTransaction.isWithdrawal()) - || savingsAccountTransaction.isReversed()) { throw new TransactionUpdateNotAllowedException(savingsId, transactionId); } + if (!(savingsAccountTransaction.isDeposit() || savingsAccountTransaction.isWithdrawal()) || savingsAccountTransaction.isReversed()) { throw new TransactionUpdateNotAllowedException( + savingsId, transactionId); } - if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, - PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException( - "error.msg.saving.account.transfer.transaction.update.not.allowed", - "Savings account transaction:" + transactionId + " update not allowed as it involves in account transfer", - transactionId); } + if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException( + "error.msg.saving.account.transfer.transaction.update.not.allowed", "Savings account transaction:" + transactionId + + " update not allowed as it involves in account transfer", transactionId); } this.savingsAccountTransactionDataValidator.validate(command); @@ -510,9 +508,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi if (account.isNotActive()) { throwValidationForActiveStatus(SavingsApiConstants.adjustTransactionAction); } - if (!account - .allowModify()) { throw new PlatformServiceUnavailableException("error.msg.saving.account.transaction.update.not.allowed", - "Savings account transaction:" + transactionId + " update not allowed for this savings type", transactionId); } + if (!account.allowModify()) { throw new PlatformServiceUnavailableException( + "error.msg.saving.account.transaction.update.not.allowed", "Savings account transaction:" + transactionId + + " update not allowed for this savings type", transactionId); } final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); @@ -553,10 +551,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi financialYearBeginningMonth); } List depositAccountOnHoldTransactions = null; - if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){ - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); } - account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction,depositAccountOnHoldTransactions); + account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction, depositAccountOnHoldTransactions); account.activateAccountBasedOnBalance(); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); return new CommandProcessingResultBuilder() // @@ -878,8 +877,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); - final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository - .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId); + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, savingsAccountId); // Get Savings account from savings charge final SavingsAccount account = savingsAccountCharge.savingsAccount(); @@ -901,11 +900,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi financialYearBeginningMonth); } List depositAccountOnHoldTransactions = null; - if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){ - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); } - account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction,depositAccountOnHoldTransactions); + account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction, + depositAccountOnHoldTransactions); this.savingAccountRepository.saveAndFlush(account); @@ -928,8 +929,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); checkClientOrGroupActive(savingsAccount); - final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository - .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId); + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, savingsAccountId); savingsAccount.removeCharge(savingsAccountCharge); this.savingAccountRepository.saveAndFlush(savingsAccount); @@ -954,8 +955,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final BigDecimal amountPaid = command.bigDecimalValueOfParameterNamed(amountParamName); final LocalDate transactionDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName); - final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository - .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId); + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, savingsAccountId); final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -994,8 +995,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi AppUser user = null; final LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); - final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository - .findOneWithNotFoundDetection(savingsAccountChargeId, accountId); + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, accountId); final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); fmt.withZone(DateUtils.getDateTimeZoneOfTenant()); @@ -1031,11 +1032,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi financialYearBeginningMonth); } List depositAccountOnHoldTransactions = null; - if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){ - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); } - account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(),depositAccountOnHoldTransactions); + account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(), + depositAccountOnHoldTransactions); this.savingAccountRepository.save(account); @@ -1064,8 +1067,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.context.authenticatedUser(); - final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository - .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId); + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, savingsAccountId); final SavingsAccount account = savingsAccountCharge.savingsAccount(); this.savingAccountAssembler.assignSavingAccountHelpers(account); @@ -1143,16 +1146,16 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final LocalDate dateOfSavingsOfficerAssignment = command.localDateValueOfParameterNamed("assignmentDate"); if (fromSavingsOfficerId != null) { - fromSavingsOfficer = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(fromSavingsOfficerId, - savingsForUpdate.office().getHierarchy()); + fromSavingsOfficer = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(fromSavingsOfficerId, savingsForUpdate + .office().getHierarchy()); } if (toSavingsOfficerId != null) { - toSavingsOfficer = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(toSavingsOfficerId, - savingsForUpdate.office().getHierarchy()); + toSavingsOfficer = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(toSavingsOfficerId, savingsForUpdate + .office().getHierarchy()); actualChanges.put("toSavingsOfficerId", toSavingsOfficer.getId()); } - if (!savingsForUpdate.hasSavingsOfficer( - fromSavingsOfficer)) { throw new SavingsOfficerAssignmentException(savingsAccountId, fromSavingsOfficerId); } + if (!savingsForUpdate.hasSavingsOfficer(fromSavingsOfficer)) { throw new SavingsOfficerAssignmentException(savingsAccountId, + fromSavingsOfficerId); } savingsForUpdate.reassignSavingsOfficer(toSavingsOfficer, dateOfSavingsOfficerAssignment); @@ -1196,6 +1199,30 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .build(); } + @Override + public CommandProcessingResult modifyWithHoldTax(Long savingsAccountId, JsonCommand command) { + final Map actualChanges = new HashMap<>(1); + final SavingsAccount savingsForUpdate = this.savingsRepository.findOneWithNotFoundDetection(savingsAccountId); + if (command.isChangeInBooleanParameterNamed(withHoldTaxParamName, savingsForUpdate.withHoldTax())) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); + actualChanges.put(withHoldTaxParamName, newValue); + savingsForUpdate.setWithHoldTax(newValue); + if (savingsForUpdate.getTaxGroup() == null) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("account"); + baseDataValidator.reset().parameter(withHoldTaxParamName).failWithCode("not.supported"); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(savingsAccountId) // + .withSavingsId(savingsAccountId) // + .with(actualChanges) // + .build(); + } + private AppUser getAppUserIfPresent() { AppUser user = null; if (this.context != null) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java index ae4055518..c29556136 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java @@ -179,6 +179,10 @@ public class SavingsEnumerations { optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue().longValue(), SavingsAccountTransactionType.OVERDRAFT_INTEREST.getCode(), "Overdraft Interest"); break; + case WITHHOLD_TAX: + optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.WITHHOLD_TAX.getValue().longValue(), + SavingsAccountTransactionType.WITHHOLD_TAX.getCode(), "Withhold Tax"); + break; } return optionData; } @@ -198,70 +202,71 @@ public class SavingsEnumerations { final boolean isPrematureClosed = type.isPreMatureClosure(); final boolean isTransferInProgress = type.isTransferInProgress(); final boolean isTransferOnHold = type.isTransferOnHold(); + final boolean matured = type.isMatured(); SavingsAccountStatusEnumData optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.INVALID.getValue().longValue(), SavingsAccountStatusType.INVALID.getCode(), "Invalid", submittedAndPendingApproval, isApproved, isRejected, - isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold); + isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured); switch (type) { case INVALID: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.INVALID.getValue().longValue(), SavingsAccountStatusType.INVALID.getCode(), "Invalid", submittedAndPendingApproval, isApproved, isRejected, - isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold); + isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured); break; case SUBMITTED_AND_PENDING_APPROVAL: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getValue() .longValue(), SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval", submittedAndPendingApproval, isApproved, isRejected, isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, - isTransferInProgress, isTransferOnHold); + isTransferInProgress, isTransferOnHold, matured); break; case REJECTED: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.REJECTED.getValue().longValue(), SavingsAccountStatusType.REJECTED.getCode(), "Rejected", submittedAndPendingApproval, isApproved, isRejected, - isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold); + isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured); break; case WITHDRAWN_BY_APPLICANT: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getValue().longValue(), SavingsAccountStatusType.WITHDRAWN_BY_APPLICANT.getCode(), "Withdrawn by applicant", submittedAndPendingApproval, isApproved, isRejected, isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, - isTransferOnHold); + isTransferOnHold, matured); break; case APPROVED: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.APPROVED.getValue().longValue(), SavingsAccountStatusType.APPROVED.getCode(), "Approved", submittedAndPendingApproval, isApproved, isRejected, - isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold); + isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured); break; case ACTIVE: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.ACTIVE.getValue().longValue(), SavingsAccountStatusType.ACTIVE.getCode(), "Active", submittedAndPendingApproval, isApproved, isRejected, - isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold); + isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured); break; case CLOSED: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.CLOSED.getValue().longValue(), SavingsAccountStatusType.CLOSED.getCode(), "Closed", submittedAndPendingApproval, isApproved, isRejected, - isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold); + isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured); break; case TRANSFER_IN_PROGRESS: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue().longValue(), SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getCode(), "Transfer in progress", submittedAndPendingApproval, isApproved, isRejected, isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, - isTransferOnHold); + isTransferOnHold, matured); break; case TRANSFER_ON_HOLD: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.TRANSFER_ON_HOLD.getValue().longValue(), SavingsAccountStatusType.TRANSFER_ON_HOLD.getCode(), "Transfer on hold", submittedAndPendingApproval, isApproved, isRejected, isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, - isTransferOnHold); + isTransferOnHold, matured); break; case PRE_MATURE_CLOSURE: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue().longValue(), SavingsAccountStatusType.PRE_MATURE_CLOSURE.getCode(), "Premature Closed", submittedAndPendingApproval, isApproved, - isRejected, isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold); + isRejected, isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured); break; case MATURED: optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.MATURED.getValue().longValue(), SavingsAccountStatusType.MATURED.getCode(), "Matured", submittedAndPendingApproval, isApproved, isRejected, - isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold); + isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured); break; default: break; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java index 9c82e160d..1fc785ee1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java @@ -34,6 +34,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.data.SavingsProductData; import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; @@ -51,7 +52,7 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead @Autowired public SavingsProductReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource, - final FineractEntityAccessUtil fineractEntityAccessUtil) { + final FineractEntityAccessUtil fineractEntityAccessUtil) { this.context = context; this.jdbcTemplate = new JdbcTemplate(dataSource); this.fineractEntityAccessUtil = fineractEntityAccessUtil; @@ -63,14 +64,14 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead this.context.authenticatedUser(); String sql = "select " + this.savingsProductRowMapper.schema() + "where sp.deposit_type_enum = ?"; - - // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office - String inClause = fineractEntityAccessUtil. - getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled( - FineractEntityType.SAVINGS_PRODUCT); - if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) { - sql += " and sp.id in ( " + inClause + " ) "; - } + + // Check if branch specific products are enabled. If yes, fetch only + // products mapped to current user's office + String inClause = fineractEntityAccessUtil + .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.SAVINGS_PRODUCT); + if ((inClause != null) && (!(inClause.trim().isEmpty()))) { + sql += " and sp.id in ( " + inClause + " ) "; + } return this.jdbcTemplate.query(sql, this.savingsProductRowMapper, new Object[] { DepositAccountType.SAVINGS_DEPOSIT.getValue() }); } @@ -79,15 +80,14 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead public Collection retrieveAllForLookup() { String sql = "select " + this.savingsProductLookupsRowMapper.schema() + " where sp.deposit_type_enum = ? "; - - // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office - String inClause = fineractEntityAccessUtil. - getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled( - FineractEntityType.SAVINGS_PRODUCT); - if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) { - sql += " and id in ( " + inClause + " ) "; - } - + + // Check if branch specific products are enabled. If yes, fetch only + // products mapped to current user's office + String inClause = fineractEntityAccessUtil + .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.SAVINGS_PRODUCT); + if ((inClause != null) && (!(inClause.trim().isEmpty()))) { + sql += " and id in ( " + inClause + " ) "; + } return this.jdbcTemplate.query(sql, this.savingsProductLookupsRowMapper, new Object[] { DepositAccountType.SAVINGS_DEPOSIT.getValue() }); @@ -132,9 +132,12 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead sqlBuilder.append("sp.min_required_balance as minRequiredBalance, "); sqlBuilder.append("sp.enforce_min_required_balance as enforceMinRequiredBalance, "); sqlBuilder.append("sp.min_balance_for_interest_calculation as minBalanceForInterestCalculation,"); - sqlBuilder.append("sp.accounting_type as accountingType "); + sqlBuilder.append("sp.accounting_type as accountingType, "); + sqlBuilder.append("sp.withhold_tax as withHoldTax,"); + sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); sqlBuilder.append("from m_savings_product sp "); sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); + sqlBuilder.append("left join m_tax_group tg on tg.id = sp.tax_group_id "); this.schemaSql = sqlBuilder.toString(); } @@ -200,11 +203,20 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); final BigDecimal minBalanceForInterestCalculation = rs.getBigDecimal("minBalanceForInterestCalculation"); + final boolean withHoldTax = rs.getBoolean("withHoldTax"); + final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId"); + final String taxGroupName = rs.getString("taxGroupName"); + TaxGroupData taxGroupData = null; + if (taxGroupId != null) { + taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); + } + return SavingsProductData.instance(id, name, shortName, description, currency, nominalAnnualInterestRate, compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, accountingRuleType, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, - minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation); + minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, + taxGroupData); } } @@ -229,22 +241,22 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead String sql = "select " + this.savingsProductLookupsRowMapper.schema(); boolean inClauseAdded = false; - - // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office - String inClause = fineractEntityAccessUtil. - getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled( - FineractEntityType.SAVINGS_PRODUCT); - if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) { - sql += " where id in ( " + inClause + " ) "; - inClauseAdded = true; - } - + + // Check if branch specific products are enabled. If yes, fetch only + // products mapped to current user's office + String inClause = fineractEntityAccessUtil + .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.SAVINGS_PRODUCT); + if ((inClause != null) && (!(inClause.trim().isEmpty()))) { + sql += " where id in ( " + inClause + " ) "; + inClauseAdded = true; + } + if (isOverdraftType != null) { - if (inClauseAdded) { - sql += " and sp.allow_overdraft=?"; - } else { - sql += " where sp.allow_overdraft=?"; - } + if (inClauseAdded) { + sql += " and sp.allow_overdraft=?"; + } else { + sql += " where sp.allow_overdraft=?"; + } return this.jdbcTemplate.query(sql, this.savingsProductLookupsRowMapper, isOverdraftType); } @@ -258,14 +270,14 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead this.context.authenticatedUser(); String sql = "select " + this.savingsProductRowMapper.schema() + " where sp.currency_code='" + currencyCode + "'"; - - // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office - String inClause = fineractEntityAccessUtil. - getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled( - FineractEntityType.SAVINGS_PRODUCT); - if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) { - sql += " and id in ( " + inClause + " ) "; - } + + // Check if branch specific products are enabled. If yes, fetch only + // products mapped to current user's office + String inClause = fineractEntityAccessUtil + .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(FineractEntityType.SAVINGS_PRODUCT); + if ((inClause != null) && (!(inClause.trim().isEmpty()))) { + sql += " and id in ( " + inClause + " ) "; + } return this.jdbcTemplate.query(sql, this.savingsProductRowMapper); } 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 159d658b7..83cfb8a38 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 @@ -18,16 +18,23 @@ */ package org.apache.fineract.portfolio.savings.service; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_PRODUCT_RESOURCE_NAME; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Set; import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessType; import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityType; @@ -40,6 +47,7 @@ import org.apache.fineract.portfolio.savings.domain.SavingsProduct; import org.apache.fineract.portfolio.savings.domain.SavingsProductAssembler; 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; @@ -63,8 +71,7 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsProductRepository savingProductRepository, final SavingsProductDataValidator fromApiJsonDataValidator, final SavingsProductAssembler savingsProductAssembler, final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService, - final FineractEntityAccessUtil fineractEntityAccessUtil - ) { + final FineractEntityAccessUtil fineractEntityAccessUtil) { this.context = context; this.savingProductRepository = savingProductRepository; this.fromApiJsonDataValidator = fromApiJsonDataValidator; @@ -116,13 +123,12 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi // save accounting mappings this.accountMappingWritePlatformService.createSavingProductToGLAccountMapping(product.getId(), command, DepositAccountType.SAVINGS_DEPOSIT); - - // check if the office specific products are enabled. If yes, then save this savings product against a specific office + + // check if the office specific products are enabled. If yes, then + // save this savings product against a specific office // i.e. this savings product is specific for this office. fineractEntityAccessUtil.checkConfigurationAndAddProductResrictionsForUserOffice( - FineractEntityAccessType.OFFICE_ACCESS_TO_SAVINGS_PRODUCTS, - FineractEntityType.SAVINGS_PRODUCT, - product.getId()); + FineractEntityAccessType.OFFICE_ACCESS_TO_SAVINGS_PRODUCTS, FineractEntityType.SAVINGS_PRODUCT, product.getId()); return new CommandProcessingResultBuilder() // .withEntityId(product.getId()) // @@ -155,6 +161,19 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi } } + if (changes.containsKey(taxGroupIdParamName)) { + final TaxGroup taxGroup = this.savingsProductAssembler.assembleTaxGroup(command); + product.setTaxGroup(taxGroup); + if (product.withHoldTax() && product.getTaxGroup() == null) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_PRODUCT_RESOURCE_NAME); + final Long taxGroupId = null; + baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank(); + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + // accounting related changes final boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName); final Map accountingMappingChanges = this.accountMappingWritePlatformService diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxApiConstants.java new file mode 100644 index 000000000..d44b5fe72 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxApiConstants.java @@ -0,0 +1,38 @@ +/** + * 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.tax.api; + +public interface TaxApiConstants { + + public static final String nameParamName = "name"; + public static final String percentageParamName = "percentage"; + public static final String debitAccountTypeParamName = "debitAccountType"; + public static final String debitAcountIdParamName = "debitAcountId"; + public static final String creditAccountTypeParamName = "creditAccountType"; + public static final String creditAcountIdParamName = "creditAcountId"; + + public static final String startDateParamName = "startDate"; + public static final String endDateParamName = "endDate"; + + public static final String chargeIncludesTaxParamName = "chargeIncludesTax"; + public static final String taxComponentsParamName = "taxComponents"; + public static final String idParamName = "id"; + public static final String taxComponentIdParamName = "taxComponentId"; + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxComponentApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxComponentApiResource.java new file mode 100644 index 000000000..9d9b5e3b0 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxComponentApiResource.java @@ -0,0 +1,140 @@ +/** + * 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.tax.api; + +import java.util.Collection; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.tax.data.TaxComponentData; +import org.apache.fineract.portfolio.tax.service.TaxReadPlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/taxes/component") +@Component +@Scope("singleton") +public class TaxComponentApiResource { + + private final String resourceNameForPermissions = "TAXCOMPONENT"; + + private final PlatformSecurityContext context; + private final TaxReadPlatformService readPlatformService; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + + @Autowired + public TaxComponentApiResource(final PlatformSecurityContext context, final TaxReadPlatformService readPlatformService, + final DefaultToApiJsonSerializer toApiJsonSerializer, + final ApiRequestParameterHelper apiRequestParameterHelper, + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + this.context = context; + this.readPlatformService = readPlatformService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.apiRequestParameterHelper = apiRequestParameterHelper; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveAllTaxComponents(@Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + final Collection TaxComponents = this.readPlatformService.retrieveAllTaxComponents(); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, TaxComponents); + } + + @GET + @Path("{taxComponentId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveTaxComponent(@PathParam("taxComponentId") final Long taxComponentId, @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + TaxComponentData taxComponentData = this.readPlatformService.retrieveTaxComponentData(taxComponentId); + return this.toApiJsonSerializer.serialize(settings, taxComponentData); + } + + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveTemplate(@Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + final TaxComponentData taxComponentData = this.readPlatformService.retrieveTaxComponentTemplate(); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, taxComponentData); + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String createTaxCompoent(final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().createTaxComponent().withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + @PUT + @Path("{taxComponentId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String updateTaxCompoent(@PathParam("taxComponentId") final Long taxComponentId, final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateTaxComponent(taxComponentId).withJson(apiRequestBodyAsJson) + .build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxGroupApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxGroupApiResource.java new file mode 100644 index 000000000..446450029 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/api/TaxGroupApiResource.java @@ -0,0 +1,142 @@ +/** + * 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.tax.api; + +import java.util.Collection; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; +import org.apache.fineract.portfolio.tax.service.TaxReadPlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/taxes/group") +@Component +@Scope("singleton") +public class TaxGroupApiResource { + + private final String resourceNameForPermissions = "TAXGROUP"; + + private final PlatformSecurityContext context; + private final TaxReadPlatformService readPlatformService; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + + @Autowired + public TaxGroupApiResource(final PlatformSecurityContext context, final TaxReadPlatformService readPlatformService, + final DefaultToApiJsonSerializer toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + this.context = context; + this.readPlatformService = readPlatformService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.apiRequestParameterHelper = apiRequestParameterHelper; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveAllTaxGroups(@Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + final Collection taxGroupDatas = this.readPlatformService.retrieveAllTaxGroups(); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, taxGroupDatas); + } + + @GET + @Path("{taxGroupId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveTaxGroup(@PathParam("taxGroupId") final Long taxGroupId, @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + TaxGroupData taxGroupData = null; + if (settings.isTemplate()) { + taxGroupData = this.readPlatformService.retrieveTaxGroupWithTemplate(taxGroupId); + } else { + taxGroupData = this.readPlatformService.retrieveTaxGroupData(taxGroupId); + } + return this.toApiJsonSerializer.serialize(settings, taxGroupData); + } + + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveTemplate(@Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + final TaxGroupData taxGroupData = this.readPlatformService.retrieveTaxGroupTemplate(); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, taxGroupData); + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String createTaxGroup(final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().createTaxGroup().withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + @PUT + @Path("{taxGroupId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String updateTaxGroup(@PathParam("taxGroupId") final Long taxGroupId, final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateTaxGroup(taxGroupId).withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentData.java new file mode 100644 index 000000000..a61ba3325 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentData.java @@ -0,0 +1,112 @@ +/** + * 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.tax.data; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.apache.fineract.accounting.glaccount.data.GLAccountData; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.joda.time.LocalDate; + +public class TaxComponentData { + + @SuppressWarnings("unused") + private final Long id; + @SuppressWarnings("unused") + private final String name; + @SuppressWarnings("unused") + private final BigDecimal percentage; + @SuppressWarnings("unused") + private final EnumOptionData debitAccountType; + @SuppressWarnings("unused") + private final GLAccountData debitAccount; + @SuppressWarnings("unused") + private final EnumOptionData creditAccountType; + @SuppressWarnings("unused") + private final GLAccountData creditAccount; + @SuppressWarnings("unused") + private final LocalDate startDate; + @SuppressWarnings("unused") + private final Collection taxComponentHistories; + + // template options + @SuppressWarnings("unused") + private final Map> glAccountOptions; + @SuppressWarnings("unused") + private final Collection glAccountTypeOptions; + + public static TaxComponentData instance(final Long id, final String name, final BigDecimal percentage, + final EnumOptionData debitAccountType, final GLAccountData debitAcount, final EnumOptionData creditAccountType, + final GLAccountData creditAcount, final LocalDate startDate, final Collection taxComponentHistories) { + final Map> glAccountOptions = null; + final Collection glAccountTypeOptions = null; + return new TaxComponentData(id, name, percentage, debitAccountType, debitAcount, creditAccountType, creditAcount, startDate, + taxComponentHistories, glAccountOptions, glAccountTypeOptions); + } + + public static TaxComponentData lookup(final Long id, final String name) { + final BigDecimal percentage = null; + final EnumOptionData debitAccountType = null; + final GLAccountData debitAcount = null; + final EnumOptionData creditAccountType = null; + final GLAccountData creditAcount = null; + final LocalDate startDate = null; + final Collection taxComponentHistories = null; + final Map> glAccountOptions = null; + final Collection glAccountTypeOptions = null; + return new TaxComponentData(id, name, percentage, debitAccountType, debitAcount, creditAccountType, creditAcount, startDate, + taxComponentHistories, glAccountOptions, glAccountTypeOptions); + } + + public static TaxComponentData template(final Map> glAccountOptions, + final Collection glAccountTypeOptions) { + final Long id = null; + final String name = null; + final BigDecimal percentage = null; + final EnumOptionData debitAccountType = null; + final GLAccountData debitAcount = null; + final EnumOptionData creditAccountType = null; + final GLAccountData creditAcount = null; + final LocalDate startDate = null; + final Collection taxComponentHistories = null; + return new TaxComponentData(id, name, percentage, debitAccountType, debitAcount, creditAccountType, creditAcount, startDate, + taxComponentHistories, glAccountOptions, glAccountTypeOptions); + } + + private TaxComponentData(final Long id, final String name, final BigDecimal percentage, final EnumOptionData debitAccountType, + final GLAccountData debitAcount, final EnumOptionData creditAccountType, final GLAccountData creditAcount, + final LocalDate startDate, final Collection taxComponentHistories, + final Map> glAccountOptions, final Collection glAccountTypeOptions) { + this.id = id; + this.percentage = percentage; + this.name = name; + this.debitAccountType = debitAccountType; + this.debitAccount = debitAcount; + this.creditAccountType = creditAccountType; + this.creditAccount = creditAcount; + this.startDate = startDate; + this.taxComponentHistories = taxComponentHistories; + this.glAccountOptions = glAccountOptions; + this.glAccountTypeOptions = glAccountTypeOptions; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentHistoryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentHistoryData.java new file mode 100644 index 000000000..400756658 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxComponentHistoryData.java @@ -0,0 +1,39 @@ +/** + * 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.tax.data; + +import java.math.BigDecimal; + +import org.joda.time.LocalDate; + +public class TaxComponentHistoryData { + + @SuppressWarnings("unused") + private final BigDecimal percentage; + @SuppressWarnings("unused") + private final LocalDate startDate; + @SuppressWarnings("unused") + private final LocalDate endDate; + + public TaxComponentHistoryData(final BigDecimal percentage, final LocalDate startDate, final LocalDate endDate) { + this.percentage = percentage; + this.startDate = startDate; + this.endDate = endDate; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupData.java new file mode 100644 index 000000000..78a35f1bc --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupData.java @@ -0,0 +1,63 @@ +/** + * 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.tax.data; + +import java.util.Collection; + +public class TaxGroupData { + + private final Long id; + private final String name; + private final Collection taxAssociations; + + // Template options + @SuppressWarnings("unused") + private final Collection taxComponents; + + public static TaxGroupData instance(final Long id, final String name, final Collection taxAssociations) { + final Collection taxComponents = null; + return new TaxGroupData(id, name, taxAssociations, taxComponents); + } + + public static TaxGroupData lookup(final Long id, final String name) { + final Collection taxComponents = null; + final Collection taxAssociations = null; + return new TaxGroupData(id, name, taxAssociations, taxComponents); + } + + public static TaxGroupData template(final Collection taxComponents) { + final Long id = null; + final String name = null; + final Collection taxAssociations = null; + return new TaxGroupData(id, name, taxAssociations, taxComponents); + } + + public static TaxGroupData template(final TaxGroupData taxGroupData, final Collection taxComponents) { + return new TaxGroupData(taxGroupData.id, taxGroupData.name, taxGroupData.taxAssociations, taxComponents); + } + + private TaxGroupData(final Long id, final String name, final Collection taxAssociations, + final Collection taxComponents) { + this.id = id; + this.name = name; + this.taxAssociations = taxAssociations; + this.taxComponents = taxComponents; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupMappingsData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupMappingsData.java new file mode 100644 index 000000000..da0249217 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/data/TaxGroupMappingsData.java @@ -0,0 +1,40 @@ +/** + * 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.tax.data; + +import org.joda.time.LocalDate; + +public class TaxGroupMappingsData { + + @SuppressWarnings("unused") + private final Long id; + @SuppressWarnings("unused") + private final TaxComponentData taxComponent; + @SuppressWarnings("unused") + private final LocalDate startDate; + @SuppressWarnings("unused") + private final LocalDate endDate; + + public TaxGroupMappingsData(final Long id, final TaxComponentData taxComponent, final LocalDate startDate, final LocalDate endDate) { + this.id = id; + this.taxComponent = taxComponent; + this.startDate = startDate; + this.endDate = endDate; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponent.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponent.java new file mode 100644 index 000000000..32ec96b2d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponent.java @@ -0,0 +1,222 @@ +/** + * 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.tax.domain; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.accounting.glaccount.domain.GLAccount; +import org.apache.fineract.accounting.glaccount.domain.GLAccountType; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.portfolio.tax.api.TaxApiConstants; +import org.apache.fineract.useradministration.domain.AppUser; +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; +import org.joda.time.LocalDate; + +@Entity +@Table(name = "m_tax_component") +public class TaxComponent extends AbstractAuditableCustom { + + @Column(name = "name", length = 100) + private String name; + + @Column(name = "percentage", scale = 6, precision = 19, nullable = false) + private BigDecimal percentage; + + @Column(name = "debit_account_type_enum") + private Integer debitAccountType; + + @ManyToOne + @JoinColumn(name = "debit_account_id") + private GLAccount debitAcount; + + @Column(name = "credit_account_type_enum") + private Integer creditAccountType; + + @ManyToOne + @JoinColumn(name = "credit_account_id") + private GLAccount creditAcount; + + @Column(name = "start_date", nullable = false) + @Temporal(TemporalType.DATE) + private Date startDate; + + @LazyCollection(LazyCollectionOption.FALSE) + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "tax_component_id", referencedColumnName = "id", nullable = false) + private List taxComponentHistories = new ArrayList<>(); + + @LazyCollection(LazyCollectionOption.TRUE) + @OneToMany(cascade = CascadeType.DETACH, mappedBy = "taxComponent", orphanRemoval = false) + private List taxGroupMappings = new ArrayList<>(); + + protected TaxComponent() { + + } + + private TaxComponent(final String name, final BigDecimal percentage, final GLAccountType debitAccountType, final GLAccount debitAcount, + final GLAccountType creditAccountType, final GLAccount creditAcount, final LocalDate startDate) { + this.name = name; + this.percentage = percentage; + if (debitAccountType != null) { + this.debitAccountType = debitAccountType.getValue(); + } + this.debitAcount = debitAcount; + if (creditAccountType != null) { + this.creditAccountType = creditAccountType.getValue(); + } + this.creditAcount = creditAcount; + this.startDate = startDate.toDate(); + } + + public static TaxComponent createTaxComponent(final String name, final BigDecimal percentage, final GLAccountType debitAccountType, + final GLAccount debitAcount, final GLAccountType creditAccountType, final GLAccount creditAcount, final LocalDate startDate) { + return new TaxComponent(name, percentage, debitAccountType, debitAcount, creditAccountType, creditAcount, startDate); + } + + public Map update(final JsonCommand command) { + final Map changes = new HashMap<>(); + + if (command.isChangeInStringParameterNamed(TaxApiConstants.nameParamName, this.name)) { + final String newValue = command.stringValueOfParameterNamed(TaxApiConstants.nameParamName); + changes.put(TaxApiConstants.nameParamName, newValue); + this.name = StringUtils.defaultIfEmpty(newValue, null); + } + + if (command.isChangeInBigDecimalParameterNamed(TaxApiConstants.percentageParamName, this.percentage)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(TaxApiConstants.percentageParamName); + changes.put(TaxApiConstants.percentageParamName, newValue); + + LocalDate oldStartDate = new LocalDate(this.startDate); + updateStartDate(command, changes, true); + LocalDate newStartDate = new LocalDate(this.startDate); + + TaxComponentHistory history = TaxComponentHistory.createTaxComponentHistory(this.percentage, oldStartDate, newStartDate); + this.taxComponentHistories.add(history); + this.percentage = newValue; + + } + + return changes; + } + + private void updateStartDate(final JsonCommand command, final Map changes, boolean setAsCurrentDate) { + LocalDate startDate = DateUtils.getLocalDateOfTenant(); + if (command.parameterExists(TaxApiConstants.startDateParamName)) { + LocalDate startDateFromUI = command.localDateValueOfParameterNamed(TaxApiConstants.startDateParamName); + if (startDateFromUI != null) { + startDate = startDateFromUI; + } + this.startDate = startDate.toDate(); + changes.put(TaxApiConstants.startDateParamName, startDate); + } else if (setAsCurrentDate) { + changes.put(TaxApiConstants.startDateParamName, startDate); + this.startDate = startDate.toDate(); + } + + } + + public BigDecimal getPercentage() { + return this.percentage; + } + + public LocalDate startDate() { + LocalDate startDate = null; + if (this.startDate != null) { + startDate = new LocalDate(this.startDate); + } + return startDate; + } + + public BigDecimal getApplicablePercentage(final LocalDate date) { + BigDecimal percentage = null; + if (occursOnDayFrom(date)) { + percentage = getPercentage(); + } else { + for (TaxComponentHistory componentHistory : taxComponentHistories) { + if (componentHistory.occursOnDayFromAndUpToAndIncluding(date)) { + percentage = componentHistory.getPercentage(); + break; + } + } + } + return percentage; + } + + private boolean occursOnDayFrom(final LocalDate target) { + return target != null && target.isAfter(startDate()); + } + + public List getTaxComponentHistories() { + return this.taxComponentHistories; + } + + public List getTaxGroupMappings() { + return this.taxGroupMappings; + } + + public Collection allStartDates() { + List dates = new ArrayList<>(); + dates.add(startDate()); + for (TaxComponentHistory componentHistory : taxComponentHistories) { + dates.add(componentHistory.startDate()); + } + + return dates; + } + + + public Integer getDebitAccountType() { + return this.debitAccountType; + } + + + public GLAccount getDebitAcount() { + return this.debitAcount; + } + + + public Integer getCreditAccountType() { + return this.creditAccountType; + } + + + public GLAccount getCreditAcount() { + return this.creditAcount; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentHistory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentHistory.java new file mode 100644 index 000000000..907489594 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentHistory.java @@ -0,0 +1,92 @@ +/** + * 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.tax.domain; + +import java.math.BigDecimal; +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; +import org.apache.fineract.useradministration.domain.AppUser; +import org.joda.time.LocalDate; + +@Entity +@Table(name = "m_tax_component_history") +public class TaxComponentHistory extends AbstractAuditableCustom { + + @Column(name = "percentage", scale = 6, precision = 19, nullable = false) + private BigDecimal percentage; + + @Column(name = "start_date", nullable = false) + @Temporal(TemporalType.DATE) + private Date startDate; + + @Column(name = "end_date", nullable = false) + @Temporal(TemporalType.DATE) + private Date endDate; + + protected TaxComponentHistory() { + + } + + private TaxComponentHistory(final BigDecimal percentage, final LocalDate startDate, final LocalDate endDate) { + this.percentage = percentage; + this.startDate = startDate.toDate(); + this.endDate = endDate.toDate(); + } + + public static TaxComponentHistory createTaxComponentHistory(final BigDecimal percentage, final LocalDate startDate, + final LocalDate endDate) { + return new TaxComponentHistory(percentage, startDate, endDate); + } + + public LocalDate startDate(){ + LocalDate startDate = null; + if(this.startDate != null){ + startDate = new LocalDate(this.startDate); + } + return startDate; + } + + public LocalDate endDate(){ + LocalDate endDate = null; + if(this.endDate != null){ + endDate = new LocalDate(this.endDate); + } + return endDate; + } + + public boolean occursOnDayFromAndUpToAndIncluding(final LocalDate target) { + if(this.endDate == null){ + return target != null && target.isAfter(startDate()); + } + return target != null && target.isAfter(startDate()) && !target.isAfter(endDate()); + } + + + public BigDecimal getPercentage() { + return this.percentage; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentRepository.java new file mode 100644 index 000000000..ee45584a8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentRepository.java @@ -0,0 +1,26 @@ +/** + * 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.tax.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface TaxComponentRepository extends JpaRepository, JpaSpecificationExecutor { + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentRepositoryWrapper.java new file mode 100644 index 000000000..ae590e714 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxComponentRepositoryWrapper.java @@ -0,0 +1,43 @@ +/** + * 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.tax.domain; + +import org.apache.fineract.portfolio.tax.exception.TaxComponentNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class TaxComponentRepositoryWrapper { + + private final TaxComponentRepository repository; + + @Autowired + public TaxComponentRepositoryWrapper(final TaxComponentRepository repository) { + this.repository = repository; + } + + public TaxComponent findOneWithNotFoundDetection(final Long id) { + + final TaxComponent taxComponent = this.repository.findOne(id); + if (taxComponent == null) { throw new TaxComponentNotFoundException(id); } + + return taxComponent; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroup.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroup.java new file mode 100644 index 000000000..0e780f394 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroup.java @@ -0,0 +1,117 @@ +/** + * 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.tax.domain; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; +import org.apache.fineract.portfolio.tax.api.TaxApiConstants; +import org.apache.fineract.portfolio.tax.exception.TaxMappingNotFoundException; +import org.apache.fineract.useradministration.domain.AppUser; +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; + +@Entity +@Table(name = "m_tax_group") +public class TaxGroup extends AbstractAuditableCustom { + + @Column(name = "name", length = 100) + private String name; + + @LazyCollection(LazyCollectionOption.FALSE) + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "tax_group_id", referencedColumnName = "id", nullable = false) + private List taxGroupMappings = new ArrayList<>(); + + protected TaxGroup() { + + } + + private TaxGroup(final String name, final List taxGroupMappings) { + this.name = name; + this.taxGroupMappings = taxGroupMappings; + } + + public static TaxGroup createTaxGroup(final String name, final List taxGroupMappings) { + return new TaxGroup(name, taxGroupMappings); + } + + public Map update(final JsonCommand command, final List taxGroupMappings) { + final Map changes = new HashMap<>(); + + if (command.isChangeInStringParameterNamed(TaxApiConstants.nameParamName, this.name)) { + final String newValue = command.stringValueOfParameterNamed(TaxApiConstants.nameParamName); + changes.put(TaxApiConstants.nameParamName, newValue); + this.name = StringUtils.defaultIfEmpty(newValue, null); + } + + List taxComponentList = new ArrayList<>(); + final List> modifications = new ArrayList<>(); + + for (TaxGroupMappings groupMappings : taxGroupMappings) { + TaxGroupMappings mappings = findOneBy(groupMappings); + if (mappings == null) { + this.taxGroupMappings.add(groupMappings); + taxComponentList.add(groupMappings.getTaxComponent().getId()); + } else { + mappings.update(groupMappings.getEndDate(), modifications); + } + } + + if (!taxComponentList.isEmpty()) { + changes.put("addComponents", taxComponentList); + } + if (!modifications.isEmpty()) { + changes.put("modifiedComponents", modifications); + } + + return changes; + } + + public TaxGroupMappings findOneBy(final TaxGroupMappings groupMapping) { + if (groupMapping.getId() != null) { + for (TaxGroupMappings groupMappings : this.taxGroupMappings) { + if (groupMappings.getId().equals(groupMapping.getId())) { return groupMappings; } + } + throw new TaxMappingNotFoundException(groupMapping.getId()); + } + return null; + } + + public List getTaxGroupMappings() { + return this.taxGroupMappings; + } + + public String getName() { + return this.name; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupMappings.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupMappings.java new file mode 100644 index 000000000..767e4bae5 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupMappings.java @@ -0,0 +1,120 @@ +/** + * 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.tax.domain; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; +import org.apache.fineract.portfolio.tax.api.TaxApiConstants; +import org.apache.fineract.useradministration.domain.AppUser; +import org.joda.time.LocalDate; + +@Entity +@Table(name = "m_tax_group_mappings") +public class TaxGroupMappings extends AbstractAuditableCustom { + + @ManyToOne + @JoinColumn(name = "tax_component_id", nullable = false) + private TaxComponent taxComponent; + + @Column(name = "start_date", nullable = false) + @Temporal(TemporalType.DATE) + private Date startDate; + + @Column(name = "end_date", nullable = true) + @Temporal(TemporalType.DATE) + private Date endDate; + + protected TaxGroupMappings() {} + + private TaxGroupMappings(final TaxComponent taxComponent, final LocalDate startDate, final LocalDate endDate) { + + this.taxComponent = taxComponent; + if (startDate != null) { + this.startDate = startDate.toDate(); + } + if (endDate != null) { + this.endDate = endDate.toDate(); + } + } + + public static TaxGroupMappings createTaxGroupMappings(final TaxComponent taxComponent, final LocalDate startDate) { + final LocalDate endDate = null; + return new TaxGroupMappings(taxComponent, startDate, endDate); + + } + + public static TaxGroupMappings createTaxGroupMappings(final Long id, final TaxComponent taxComponent, final LocalDate endDate) { + final LocalDate startDate = null; + TaxGroupMappings groupMappings = new TaxGroupMappings(taxComponent, startDate, endDate); + groupMappings.setId(id); + return groupMappings; + + } + + public void update(final Date endDate, final List> changes) { + if (endDate != null && this.endDate == null) { + this.endDate = endDate; + Map map = new HashMap<>(2); + map.put(TaxApiConstants.endDateParamName, endDate); + map.put(TaxApiConstants.taxComponentIdParamName, this.getTaxComponent().getId()); + changes.add(map); + } + } + + public boolean occursOnDayFromAndUpToAndIncluding(final LocalDate target) { + if (this.endDate == null) { return target != null && target.isAfter(startDate()); } + return target != null && target.isAfter(startDate()) && !target.isAfter(endDate()); + } + + public TaxComponent getTaxComponent() { + return this.taxComponent; + } + + public Date getEndDate() { + return this.endDate; + } + + public LocalDate startDate() { + LocalDate startDate = null; + if (this.startDate != null) { + startDate = new LocalDate(this.startDate); + } + return startDate; + } + + public LocalDate endDate() { + LocalDate endDate = null; + if (this.endDate != null) { + endDate = new LocalDate(this.endDate); + } + return endDate; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupRepository.java new file mode 100644 index 000000000..993454124 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupRepository.java @@ -0,0 +1,26 @@ +/** + * 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.tax.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface TaxGroupRepository extends JpaRepository, JpaSpecificationExecutor { + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupRepositoryWrapper.java new file mode 100644 index 000000000..5ef1776c2 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/domain/TaxGroupRepositoryWrapper.java @@ -0,0 +1,43 @@ +/** + * 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.tax.domain; + +import org.apache.fineract.portfolio.tax.exception.TaxGroupNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class TaxGroupRepositoryWrapper { + + private final TaxGroupRepository repository; + + @Autowired + public TaxGroupRepositoryWrapper(final TaxGroupRepository repository) { + this.repository = repository; + } + + public TaxGroup findOneWithNotFoundDetection(final Long id) { + + final TaxGroup taxGroup = this.repository.findOne(id); + if (taxGroup == null) { throw new TaxGroupNotFoundException(id); } + + return taxGroup; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxComponentNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxComponentNotFoundException.java new file mode 100644 index 000000000..0cae305ef --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxComponentNotFoundException.java @@ -0,0 +1,28 @@ +/** + * 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.tax.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class TaxComponentNotFoundException extends AbstractPlatformResourceNotFoundException { + + public TaxComponentNotFoundException(final Long id) { + super("error.msg.tax.component.id.invalid", "tax component with identifier " + id + " does not exist", id); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxGroupNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxGroupNotFoundException.java new file mode 100644 index 000000000..76ff42f2c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxGroupNotFoundException.java @@ -0,0 +1,28 @@ +/** + * 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.tax.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class TaxGroupNotFoundException extends AbstractPlatformResourceNotFoundException { + + public TaxGroupNotFoundException(final Long id) { + super("error.msg.tax.group.id.invalid", "tax group with identifier " + id + " does not exist", id); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxMappingNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxMappingNotFoundException.java new file mode 100644 index 000000000..d9ce11f18 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/exception/TaxMappingNotFoundException.java @@ -0,0 +1,34 @@ +/** + * 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.tax.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +/** + * {@link AbstractPlatformDomainRuleException} thrown when tax group mapping + * does not exist. + */ +public class TaxMappingNotFoundException extends AbstractPlatformResourceNotFoundException { + + public TaxMappingNotFoundException(final Long id) { + super("error.msg.tax.group.id.invalid", "Tax group mapping with identifier " + id + " does not exist", id); + } + +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/CreateTaxComponentCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/CreateTaxComponentCommandHandler.java new file mode 100644 index 000000000..cabf0caf8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/CreateTaxComponentCommandHandler.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.portfolio.tax.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.tax.service.TaxWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@CommandType(entity = "TAXCOMPONENT", action = "CREATE") +public class CreateTaxComponentCommandHandler implements NewCommandSourceHandler { + + private final TaxWritePlatformService taxWritePlatformService; + + @Autowired + public CreateTaxComponentCommandHandler(final TaxWritePlatformService taxWritePlatformService) { + this.taxWritePlatformService = taxWritePlatformService; + } + + @Override + public CommandProcessingResult processCommand(JsonCommand jsonCommand) { + return this.taxWritePlatformService.createTaxComponent(jsonCommand); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/CreateTaxGroupCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/CreateTaxGroupCommandHandler.java new file mode 100644 index 000000000..e5b523d9e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/CreateTaxGroupCommandHandler.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.portfolio.tax.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.tax.service.TaxWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@CommandType(entity = "TAXGROUP", action = "CREATE") +public class CreateTaxGroupCommandHandler implements NewCommandSourceHandler { + + private final TaxWritePlatformService taxWritePlatformService; + + @Autowired + public CreateTaxGroupCommandHandler(final TaxWritePlatformService taxWritePlatformService) { + this.taxWritePlatformService = taxWritePlatformService; + } + + @Override + public CommandProcessingResult processCommand(JsonCommand jsonCommand) { + return this.taxWritePlatformService.createTaxGroup(jsonCommand); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/UpdateTaxComponentCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/UpdateTaxComponentCommandHandler.java new file mode 100644 index 000000000..b94e369f2 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/UpdateTaxComponentCommandHandler.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.portfolio.tax.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.tax.service.TaxWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@CommandType(entity = "TAXCOMPONENT", action = "UPDATE") +public class UpdateTaxComponentCommandHandler implements NewCommandSourceHandler { + + private final TaxWritePlatformService taxWritePlatformService; + + @Autowired + public UpdateTaxComponentCommandHandler(final TaxWritePlatformService taxWritePlatformService) { + this.taxWritePlatformService = taxWritePlatformService; + } + + @Override + public CommandProcessingResult processCommand(JsonCommand jsonCommand) { + return this.taxWritePlatformService.updateTaxComponent(jsonCommand.entityId(), jsonCommand); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/UpdateTaxGroupCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/UpdateTaxGroupCommandHandler.java new file mode 100644 index 000000000..1229e460d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/handler/UpdateTaxGroupCommandHandler.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.portfolio.tax.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.tax.service.TaxWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@CommandType(entity = "TAXGROUP", action = "UPDATE") +public class UpdateTaxGroupCommandHandler implements NewCommandSourceHandler { + + private final TaxWritePlatformService taxWritePlatformService; + + @Autowired + public UpdateTaxGroupCommandHandler(final TaxWritePlatformService taxWritePlatformService) { + this.taxWritePlatformService = taxWritePlatformService; + } + + @Override + public CommandProcessingResult processCommand(JsonCommand jsonCommand) { + return this.taxWritePlatformService.updateTaxGroup(jsonCommand.entityId(), jsonCommand); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/serialization/TaxValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/serialization/TaxValidator.java new file mode 100644 index 000000000..6df786e94 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/serialization/TaxValidator.java @@ -0,0 +1,365 @@ +/** + * 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.tax.serialization; + +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.accounting.glaccount.domain.GLAccountType; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +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.infrastructure.core.service.DateUtils; +import org.apache.fineract.portfolio.tax.api.TaxApiConstants; +import org.apache.fineract.portfolio.tax.domain.TaxComponent; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; +import org.apache.fineract.portfolio.tax.domain.TaxGroupMappings; +import org.joda.time.LocalDate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; + +@Component +public class TaxValidator { + + final Set supportedTaxComponentCreateParameters = new HashSet<>(Arrays.asList("dateFormat", "locale", + TaxApiConstants.nameParamName, TaxApiConstants.percentageParamName, TaxApiConstants.startDateParamName, + TaxApiConstants.debitAccountTypeParamName, TaxApiConstants.debitAcountIdParamName, TaxApiConstants.creditAccountTypeParamName, + TaxApiConstants.creditAcountIdParamName)); + + final Set supportedTaxComponentUpdateParameters = new HashSet<>(Arrays.asList("dateFormat", "locale", + TaxApiConstants.nameParamName, TaxApiConstants.percentageParamName, TaxApiConstants.startDateParamName)); + + final Set supportedTaxGroupParameters = new HashSet<>(Arrays.asList("dateFormat", "locale", TaxApiConstants.nameParamName, + TaxApiConstants.taxComponentsParamName)); + + final Set supportedTaxGroupTaxComponentsCreateParameters = new HashSet<>(Arrays.asList(TaxApiConstants.taxComponentIdParamName, + TaxApiConstants.startDateParamName)); + + final Set supportedTaxGroupTaxComponentsUpdateParameters = new HashSet<>(Arrays.asList(TaxApiConstants.idParamName, + TaxApiConstants.taxComponentIdParamName, TaxApiConstants.startDateParamName, TaxApiConstants.endDateParamName)); + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public TaxValidator(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public void validateForTaxComponentCreate(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedTaxComponentCreateParameters); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("tax.component"); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final String name = this.fromApiJsonHelper.extractStringNamed(TaxApiConstants.nameParamName, element); + baseDataValidator.reset().parameter(TaxApiConstants.nameParamName).value(name).notBlank(); + + final BigDecimal percentage = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(TaxApiConstants.percentageParamName, element); + baseDataValidator.reset().parameter(TaxApiConstants.percentageParamName).value(percentage).notBlank().positiveAmount() + .notGreaterThanMax(BigDecimal.valueOf(100)); + + final Integer debitAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(TaxApiConstants.debitAccountTypeParamName, + element); + baseDataValidator + .reset() + .parameter(TaxApiConstants.debitAccountTypeParamName) + .value(debitAccountType) + .ignoreIfNull() + .isOneOfTheseValues(GLAccountType.ASSET.getValue(), GLAccountType.LIABILITY.getValue(), GLAccountType.EQUITY.getValue(), + GLAccountType.INCOME.getValue(), GLAccountType.EXPENSE.getValue()); + + final Long debitAccountId = this.fromApiJsonHelper.extractLongNamed(TaxApiConstants.debitAcountIdParamName, element); + baseDataValidator.reset().parameter(TaxApiConstants.debitAcountIdParamName).value(debitAccountId).longGreaterThanZero(); + if (debitAccountType != null || debitAccountId != null) { + baseDataValidator.reset().parameter(TaxApiConstants.debitAccountTypeParamName).value(debitAccountType).notBlank(); + baseDataValidator.reset().parameter(TaxApiConstants.debitAcountIdParamName).value(debitAccountId).notBlank(); + } + + final Integer creditAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(TaxApiConstants.creditAccountTypeParamName, + element); + baseDataValidator + .reset() + .parameter(TaxApiConstants.creditAccountTypeParamName) + .value(creditAccountType) + .ignoreIfNull() + .isOneOfTheseValues(GLAccountType.ASSET.getValue(), GLAccountType.LIABILITY.getValue(), GLAccountType.EQUITY.getValue(), + GLAccountType.INCOME.getValue(), GLAccountType.EXPENSE.getValue()); + + final Long creditAccountId = this.fromApiJsonHelper.extractLongNamed(TaxApiConstants.creditAcountIdParamName, element); + baseDataValidator.reset().parameter(TaxApiConstants.creditAcountIdParamName).value(creditAccountId).longGreaterThanZero(); + if (creditAccountType != null || creditAccountId != null) { + baseDataValidator.reset().parameter(TaxApiConstants.creditAcountIdParamName).value(creditAccountId).notBlank(); + baseDataValidator.reset().parameter(TaxApiConstants.creditAccountTypeParamName).value(creditAccountType).notBlank(); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateForTaxComponentUpdate(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedTaxComponentUpdateParameters); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("tax.component"); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + if (this.fromApiJsonHelper.parameterExists(TaxApiConstants.nameParamName, element)) { + final String name = this.fromApiJsonHelper.extractStringNamed(TaxApiConstants.nameParamName, element); + baseDataValidator.reset().parameter(TaxApiConstants.nameParamName).value(name).notBlank(); + } + + if (this.fromApiJsonHelper.parameterExists(TaxApiConstants.percentageParamName, element)) { + final BigDecimal percentage = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(TaxApiConstants.percentageParamName, + element); + baseDataValidator.reset().parameter(TaxApiConstants.percentageParamName).value(percentage).notBlank().positiveAmount() + .notGreaterThanMax(BigDecimal.valueOf(100)); + } + + if (this.fromApiJsonHelper.parameterExists(TaxApiConstants.startDateParamName, element)) { + final LocalDate startDate = this.fromApiJsonHelper.extractLocalDateNamed(TaxApiConstants.startDateParamName, element); + baseDataValidator.reset().parameter(TaxApiConstants.startDateParamName).value(startDate) + .validateDateAfter(DateUtils.getLocalDateOfTenant()); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateForTaxGroupCreate(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedTaxGroupParameters); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("tax.group"); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final String name = this.fromApiJsonHelper.extractStringNamed(TaxApiConstants.nameParamName, element); + baseDataValidator.reset().parameter(TaxApiConstants.nameParamName).value(name).notBlank(); + + final JsonArray taxComponents = this.fromApiJsonHelper.extractJsonArrayNamed(TaxApiConstants.taxComponentsParamName, element); + baseDataValidator.reset().parameter(TaxApiConstants.taxComponentsParamName).value(taxComponents).notBlank(); + + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + if (topLevelJsonElement.get(TaxApiConstants.taxComponentsParamName).isJsonArray()) { + final JsonArray array = topLevelJsonElement.get(TaxApiConstants.taxComponentsParamName).getAsJsonArray(); + baseDataValidator.reset().parameter(TaxApiConstants.taxComponentsParamName).value(array.size()).integerGreaterThanZero(); + for (int i = 1; i <= array.size(); i++) { + final JsonObject taxComponent = array.get(i - 1).getAsJsonObject(); + final String arrayObjectJson = this.fromApiJsonHelper.toJson(taxComponent); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, arrayObjectJson, + supportedTaxGroupTaxComponentsCreateParameters); + final Long taxComponentId = this.fromApiJsonHelper.extractLongNamed(TaxApiConstants.taxComponentIdParamName, taxComponent); + baseDataValidator.reset().parameter(TaxApiConstants.taxComponentsParamName) + .parameterAtIndexArray(TaxApiConstants.taxComponentIdParamName, i).value(taxComponentId).notNull() + .longGreaterThanZero(); + + } + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateForTaxGroupUpdate(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedTaxGroupParameters); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("tax.group"); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + if (this.fromApiJsonHelper.parameterExists(TaxApiConstants.nameParamName, element)) { + final String name = this.fromApiJsonHelper.extractStringNamed(TaxApiConstants.nameParamName, element); + baseDataValidator.reset().parameter(TaxApiConstants.nameParamName).value(name).notBlank(); + } + if (this.fromApiJsonHelper.parameterExists(TaxApiConstants.taxComponentsParamName, element)) { + final JsonArray taxComponents = this.fromApiJsonHelper.extractJsonArrayNamed(TaxApiConstants.taxComponentsParamName, element); + baseDataValidator.reset().parameter(TaxApiConstants.taxComponentsParamName).value(taxComponents).notBlank(); + + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); + final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + if (topLevelJsonElement.get(TaxApiConstants.taxComponentsParamName).isJsonArray()) { + final JsonArray array = topLevelJsonElement.get(TaxApiConstants.taxComponentsParamName).getAsJsonArray(); + for (int i = 1; i <= array.size(); i++) { + final JsonObject taxComponent = array.get(i - 1).getAsJsonObject(); + final String arrayObjectJson = this.fromApiJsonHelper.toJson(taxComponent); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, arrayObjectJson, + supportedTaxGroupTaxComponentsUpdateParameters); + final Long taxComponentId = this.fromApiJsonHelper.extractLongNamed(TaxApiConstants.taxComponentIdParamName, + taxComponent); + final Long taxMappingId = this.fromApiJsonHelper + .extractLongNamed(TaxApiConstants.taxComponentIdParamName, taxComponent); + if (taxMappingId == null) { + baseDataValidator + .reset() + .parameter( + TaxApiConstants.taxComponentsParamName + "." + TaxApiConstants.taxComponentIdParamName + + ".at.index." + i).value(taxComponentId).notNull().longGreaterThanZero(); + } else { + baseDataValidator + .reset() + .parameter( + TaxApiConstants.taxComponentsParamName + "." + TaxApiConstants.taxComponentIdParamName + + ".at.index." + i).value(taxComponentId).longGreaterThanZero(); + baseDataValidator.reset() + .parameter(TaxApiConstants.taxComponentsParamName + "." + TaxApiConstants.idParamName + ".at.index." + i) + .value(taxMappingId).longGreaterThanZero(); + } + + final LocalDate endDate = this.fromApiJsonHelper.extractLocalDateNamed(TaxApiConstants.endDateParamName, taxComponent, + dateFormat, locale); + baseDataValidator.reset() + .parameter(TaxApiConstants.taxComponentsParamName + "." + TaxApiConstants.endDateParamName + ".at.index." + i) + .value(endDate).ignoreIfNull().validateDateAfter(DateUtils.getLocalDateOfTenant()); + final LocalDate startDate = this.fromApiJsonHelper.extractLocalDateNamed(TaxApiConstants.startDateParamName, + taxComponent, dateFormat, locale); + if (endDate != null && startDate != null) { + baseDataValidator.reset().parameter(TaxApiConstants.taxComponentsParamName + ".at.index." + i) + .failWithCode("start.date.end.date.both.should.not.be.present", startDate, endDate); + } + } + } + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateTaxGroupEndDateAndTaxComponent(final TaxGroup taxGroup, final List groupMappings) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("tax.group"); + + for (TaxGroupMappings mapping : groupMappings) { + if (mapping.getId() != null) { + TaxGroupMappings existing = taxGroup.findOneBy(mapping); + if (existing.endDate() != null && mapping.endDate() != null && !existing.endDate().isEqual(mapping.endDate())) { + baseDataValidator.reset().parameter(TaxApiConstants.endDateParamName) + .failWithCode("can.not.modify.end.date.once.updated"); + } else { + baseDataValidator.reset().parameter(TaxApiConstants.endDateParamName).value(mapping.endDate()).ignoreIfNull() + .validateDateAfter(existing.startDate()); + } + if(mapping.getTaxComponent()!= null && !existing.getTaxComponent().getId().equals(mapping.getTaxComponent().getId())){ + baseDataValidator.reset().parameter(TaxApiConstants.taxComponentIdParamName).failWithCode("update.not.supported");; + } + } else if (mapping.endDate() != null) { + baseDataValidator.reset().parameter(TaxApiConstants.endDateParamName).failWithCode("not.supported.for.new.association"); + } + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateTaxGroup(final TaxGroup taxGroup) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("tax.group"); + List groupMappings = taxGroup.getTaxGroupMappings(); + validateGroupTotal(groupMappings, baseDataValidator, "total.percentage"); + validateOverlappingComponents(groupMappings, baseDataValidator); + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateTaxComponentForUpdate(final TaxComponent taxComponent) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("tax.component"); + validateGroupTotal(taxComponent.getTaxGroupMappings(), baseDataValidator, "group.total." + TaxApiConstants.percentageParamName); + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateStartDate(final LocalDate existingStartDate, final JsonCommand command) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("tax.component"); + validateStartDate(existingStartDate, command.localDateValueOfParameterNamed(TaxApiConstants.startDateParamName), baseDataValidator); + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void validateStartDate(final LocalDate existingStartDate, final LocalDate startDate, + final DataValidatorBuilder baseDataValidator) { + baseDataValidator.reset().parameter(TaxApiConstants.startDateParamName).value(startDate).validateDateAfter(existingStartDate); + } + + private void validateOverlappingComponents(final List taxMappings, final DataValidatorBuilder baseDataValidator) { + for (TaxGroupMappings groupMappingsOne : taxMappings) { + final List mappings = new ArrayList<>(taxMappings); + mappings.remove(groupMappingsOne); + for (TaxGroupMappings groupMappings : mappings) { + if (groupMappingsOne.getTaxComponent().equals(groupMappings.getTaxComponent())) { + if (groupMappingsOne.endDate() == null && groupMappings.endDate() == null) { + baseDataValidator.reset().parameter("component").failWithCode("dates.are.overlapping"); + } else if (groupMappingsOne.startDate().isAfter(groupMappings.startDate())) { + baseDataValidator.reset().parameter("component.start.date").value(groupMappingsOne.startDate()) + .validateDateAfter(groupMappings.endDate()); + } else { + baseDataValidator.reset().parameter("component.start.date").value(groupMappings.startDate()) + .validateDateAfter(groupMappingsOne.endDate()); + } + } + } + } + } + + private void validateGroupTotal(final List taxMappings, final DataValidatorBuilder baseDataValidator, + final String paramenter) { + for (TaxGroupMappings groupMappingsOne : taxMappings) { + Collection dates = groupMappingsOne.getTaxComponent().allStartDates(); + for (LocalDate date : dates) { + LocalDate applicableDate = date.plusDays(1); + BigDecimal total = BigDecimal.ZERO; + for (TaxGroupMappings groupMappings : taxMappings) { + if (groupMappings.occursOnDayFromAndUpToAndIncluding(applicableDate)) { + BigDecimal applicablePercentage = groupMappings.getTaxComponent().getApplicablePercentage(applicableDate); + if (applicablePercentage != null) { + total = total.add(applicablePercentage); + } + } + } + baseDataValidator.reset().parameter(paramenter).value(total).notGreaterThanMax(BigDecimal.valueOf(100)); + } + } + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxAssembler.java new file mode 100644 index 000000000..1e5ad9206 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxAssembler.java @@ -0,0 +1,163 @@ +/** + * 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.tax.service; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.apache.fineract.accounting.glaccount.domain.GLAccount; +import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper; +import org.apache.fineract.accounting.glaccount.domain.GLAccountType; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.portfolio.tax.api.TaxApiConstants; +import org.apache.fineract.portfolio.tax.domain.TaxComponent; +import org.apache.fineract.portfolio.tax.domain.TaxComponentRepositoryWrapper; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; +import org.apache.fineract.portfolio.tax.domain.TaxGroupMappings; +import org.joda.time.LocalDate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +@Component +public class TaxAssembler { + + private final FromJsonHelper fromApiJsonHelper; + private final GLAccountRepositoryWrapper glAccountRepositoryWrapper; + private final TaxComponentRepositoryWrapper taxComponentRepositoryWrapper; + + @Autowired + public TaxAssembler(final FromJsonHelper fromApiJsonHelper, final GLAccountRepositoryWrapper glAccountRepositoryWrapper, + final TaxComponentRepositoryWrapper taxComponentRepositoryWrapper) { + this.fromApiJsonHelper = fromApiJsonHelper; + this.glAccountRepositoryWrapper = glAccountRepositoryWrapper; + this.taxComponentRepositoryWrapper = taxComponentRepositoryWrapper; + } + + public TaxComponent assembleTaxComponentFrom(final JsonCommand command) { + final JsonElement element = command.parsedJson(); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("tax.component"); + + final String name = this.fromApiJsonHelper.extractStringNamed(TaxApiConstants.nameParamName, element); + final BigDecimal percentage = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(TaxApiConstants.percentageParamName, element); + final Integer debitAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(TaxApiConstants.debitAccountTypeParamName, + element); + final Long debitAccountId = this.fromApiJsonHelper.extractLongNamed(TaxApiConstants.debitAcountIdParamName, element); + GLAccountType debitGlAccountType = null; + if (debitAccountType != null) { + debitGlAccountType = GLAccountType.fromInt(debitAccountType); + } + GLAccount debitGlAccount = null; + if (debitAccountId != null) { + debitGlAccount = this.glAccountRepositoryWrapper.findOneWithNotFoundDetection(debitAccountId); + if (!debitGlAccount.getType().equals(debitAccountType) || debitGlAccount.isHeaderAccount()) { + baseDataValidator.parameter(TaxApiConstants.debitAcountIdParamName).value(debitAccountId) + .failWithCode("not.a.valid.account"); + } + } + + final Integer creditAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(TaxApiConstants.creditAccountTypeParamName, + element); + GLAccountType creditGlAccountType = null; + if (creditAccountType != null) { + creditGlAccountType = GLAccountType.fromInt(creditAccountType); + } + final Long creditAccountId = this.fromApiJsonHelper.extractLongNamed(TaxApiConstants.creditAcountIdParamName, element); + GLAccount creditGlAccount = null; + if (creditAccountId != null) { + creditGlAccount = this.glAccountRepositoryWrapper.findOneWithNotFoundDetection(creditAccountId); + if (!creditGlAccount.getType().equals(creditAccountType) || creditGlAccount.isHeaderAccount()) { + baseDataValidator.parameter(TaxApiConstants.creditAcountIdParamName).value(creditAccountId) + .failWithCode("not.a.valid.account"); + } + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); + LocalDate startDate = this.fromApiJsonHelper.extractLocalDateNamed(TaxApiConstants.startDateParamName, element); + if (startDate == null) { + startDate = DateUtils.getLocalDateOfTenant(); + } + + return TaxComponent.createTaxComponent(name, percentage, debitGlAccountType, debitGlAccount, creditGlAccountType, creditGlAccount, + startDate); + } + + public TaxGroup assembleTaxGroupFrom(final JsonCommand command) { + final JsonElement element = command.parsedJson(); + + final String name = this.fromApiJsonHelper.extractStringNamed(TaxApiConstants.nameParamName, element); + boolean isUpdate = false; + final List groupMappings = assembleTaxGroupMappingsFrom(command, isUpdate); + return TaxGroup.createTaxGroup(name, groupMappings); + } + + public List assembleTaxGroupMappingsFrom(final JsonCommand command, boolean isUpdate) { + List groupMappings = new ArrayList<>(); + final JsonElement element = command.parsedJson(); + + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); + final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + + if (topLevelJsonElement.get(TaxApiConstants.taxComponentsParamName).isJsonArray()) { + final JsonArray array = topLevelJsonElement.get(TaxApiConstants.taxComponentsParamName).getAsJsonArray(); + for (int i = 0; i < array.size(); i++) { + final JsonObject taxComponent = array.get(i).getAsJsonObject(); + final Long mappingId = this.fromApiJsonHelper.extractLongNamed(TaxApiConstants.idParamName, taxComponent); + final Long taxComponentId = this.fromApiJsonHelper.extractLongNamed(TaxApiConstants.taxComponentIdParamName, taxComponent); + TaxComponent component = null; + if(taxComponentId != null){ + component = this.taxComponentRepositoryWrapper.findOneWithNotFoundDetection(taxComponentId); + } + LocalDate startDate = this.fromApiJsonHelper.extractLocalDateNamed(TaxApiConstants.startDateParamName, taxComponent, + dateFormat, locale); + final LocalDate endDate = this.fromApiJsonHelper.extractLocalDateNamed(TaxApiConstants.endDateParamName, taxComponent, + dateFormat, locale); + if (endDate == null && startDate == null) { + startDate = DateUtils.getLocalDateOfTenant(); + } + TaxGroupMappings mappings = null; + if (isUpdate && mappingId != null) { + mappings = TaxGroupMappings.createTaxGroupMappings(mappingId, component, endDate); + } else { + mappings = TaxGroupMappings.createTaxGroupMappings(component, startDate); + } + groupMappings.add(mappings); + + } + } + + return groupMappings; + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformService.java new file mode 100644 index 000000000..646255c63 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformService.java @@ -0,0 +1,44 @@ +/** + * 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.tax.service; + +import java.util.Collection; + +import org.apache.fineract.portfolio.tax.data.TaxComponentData; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; + +public interface TaxReadPlatformService { + + public TaxComponentData retrieveTaxComponentData(final Long id); + + public TaxComponentData retrieveTaxComponentTemplate(); + + public TaxGroupData retrieveTaxGroupData(final Long id); + + public TaxGroupData retrieveTaxGroupWithTemplate(final Long id); + + public TaxGroupData retrieveTaxGroupTemplate(); + + Collection retrieveAllTaxComponents(); + + Collection retrieveAllTaxGroups(); + + Collection retrieveTaxGroupsForLookUp(); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformServiceImpl.java new file mode 100644 index 000000000..743efd6a1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxReadPlatformServiceImpl.java @@ -0,0 +1,305 @@ +/** + * 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.tax.service; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService; +import org.apache.fineract.accounting.common.AccountingEnumerations; +import org.apache.fineract.accounting.glaccount.data.GLAccountData; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.domain.JdbcSupport; +import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.apache.fineract.portfolio.tax.data.TaxComponentData; +import org.apache.fineract.portfolio.tax.data.TaxComponentHistoryData; +import org.apache.fineract.portfolio.tax.data.TaxGroupData; +import org.apache.fineract.portfolio.tax.data.TaxGroupMappingsData; +import org.joda.time.LocalDate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class TaxReadPlatformServiceImpl implements TaxReadPlatformService { + + final TaxComponentMapper taxComponentMapper = new TaxComponentMapper(); + final TaxGroupMapper taxGroupMapper = new TaxGroupMapper(); + final TaxComponentLookUpMapper taxComponentLookUpMapper = new TaxComponentLookUpMapper(); + final TaxGroupLookUpMapper taxGroupLookUpMapper = new TaxGroupLookUpMapper(); + + private final JdbcTemplate jdbcTemplate; + private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService; + + @Autowired + public TaxReadPlatformServiceImpl(final RoutingDataSource dataSource, + final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService; + } + + @Override + public Collection retrieveAllTaxComponents() { + String sql = "select " + this.taxComponentMapper.getSchema(); + return this.jdbcTemplate.query(sql, this.taxComponentMapper); + } + + @Override + public TaxComponentData retrieveTaxComponentData(final Long id) { + String sql = "select " + this.taxComponentMapper.getSchema() + " where tc.id=?"; + return this.jdbcTemplate.queryForObject(sql, this.taxComponentMapper, new Object[] { id }); + } + + @Override + public TaxComponentData retrieveTaxComponentTemplate() { + return TaxComponentData.template(this.accountingDropdownReadPlatformService.retrieveAccountMappingOptions(), + this.accountingDropdownReadPlatformService.retrieveGLAccountTypeOptions()); + } + + @Override + public Collection retrieveAllTaxGroups() { + String sql = "select " + this.taxGroupMapper.getSchema(); + return this.jdbcTemplate.query(sql, this.taxGroupMapper); + } + + @Override + public TaxGroupData retrieveTaxGroupData(final Long id) { + String sql = "select " + this.taxGroupMapper.getSchema() + " where tg.id=?"; + return this.jdbcTemplate.queryForObject(sql, this.taxGroupMapper, new Object[] { id }); + } + + @Override + public TaxGroupData retrieveTaxGroupWithTemplate(final Long id) { + TaxGroupData taxGroupData = retrieveTaxGroupData(id); + taxGroupData = TaxGroupData.template(taxGroupData, retrieveTaxComponentsForLookUp()); + return taxGroupData; + } + + @Override + public TaxGroupData retrieveTaxGroupTemplate() { + return TaxGroupData.template(retrieveTaxComponentsForLookUp()); + } + + private Collection retrieveTaxComponentsForLookUp() { + String sql = "select " + this.taxComponentLookUpMapper.getSchema(); + return this.jdbcTemplate.query(sql, this.taxComponentLookUpMapper); + } + + @Override + public Collection retrieveTaxGroupsForLookUp() { + String sql = "select " + this.taxGroupLookUpMapper.getSchema(); + return this.jdbcTemplate.query(sql, this.taxGroupLookUpMapper); + } + + private static final class TaxComponentMapper implements RowMapper { + + private final String schema; + private TaxComponentHistoryDataMapper componentHistoryDataMapper = new TaxComponentHistoryDataMapper(); + + public TaxComponentMapper() { + StringBuilder sb = new StringBuilder(); + sb.append("tc.id as id, tc.name as name,"); + sb.append("tc.percentage as percentage, tc.start_date as startDate,"); + sb.append("tc.debit_account_type_enum as debitAccountTypeEnum,"); + sb.append("dgl.id as debitAccountId, dgl.name as debitAccountName, dgl.gl_code as debitAccountGlCode,"); + sb.append("tc.credit_account_type_enum as creditAccountTypeEnum,"); + sb.append("cgl.id as creditAccountId, cgl.name as creditAccountName, cgl.gl_code as creditAccountGlCode,"); + sb.append("history.percentage as historyPercentage, history.start_date as historyStartDate,"); + sb.append("history.end_date as historyEndDate"); + sb.append(" from m_tax_component tc "); + sb.append(" left join acc_gl_account dgl on dgl.id = tc.debit_account_id"); + sb.append(" left join acc_gl_account cgl on cgl.id = tc.credit_account_id"); + sb.append(" left join m_tax_component_history history on history.tax_component_id = tc.id"); + + this.schema = sb.toString(); + } + + @Override + public TaxComponentData mapRow(ResultSet rs, int rowNum) throws SQLException { + final Long id = rs.getLong("id"); + final String name = rs.getString("name"); + final BigDecimal percentage = rs.getBigDecimal("percentage"); + final Integer debitAccountTypeEnum = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "debitAccountTypeEnum"); + EnumOptionData debitAccountType = null; + if (debitAccountTypeEnum != null) { + debitAccountType = AccountingEnumerations.gLAccountType(debitAccountTypeEnum); + } + GLAccountData debitAccountData = null; + if (debitAccountTypeEnum != null && debitAccountTypeEnum > 0) { + final Long debitAccountId = rs.getLong("debitAccountId"); + final String debitAccountName = rs.getString("debitAccountName"); + final String debitAccountGlCode = rs.getString("debitAccountGlCode"); + debitAccountData = new GLAccountData(debitAccountId, debitAccountName, debitAccountGlCode); + } + + final Integer creditAccountTypeEnum = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "creditAccountTypeEnum"); + EnumOptionData creditAccountType = null; + if (creditAccountTypeEnum != null) { + creditAccountType = AccountingEnumerations.gLAccountType(creditAccountTypeEnum); + } + GLAccountData creditAccountData = null; + if (creditAccountTypeEnum != null && creditAccountTypeEnum > 0) { + final Long creditAccountId = rs.getLong("creditAccountId"); + final String creditAccountName = rs.getString("creditAccountName"); + final String creditAccountGlCode = rs.getString("creditAccountGlCode"); + creditAccountData = new GLAccountData(creditAccountId, creditAccountName, creditAccountGlCode); + } + final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate"); + + Collection historyDatas = new ArrayList<>(); + historyDatas.add(componentHistoryDataMapper.mapRow(rs, rowNum)); + while (rs.next()) { + if (id.equals(rs.getLong("id"))) { + historyDatas.add(componentHistoryDataMapper.mapRow(rs, rowNum)); + } else { + rs.previous(); + break; + } + } + return TaxComponentData.instance(id, name, percentage, debitAccountType, debitAccountData, creditAccountType, + creditAccountData, startDate, historyDatas); + } + + public String getSchema() { + return this.schema; + } + + } + + private static final class TaxComponentHistoryDataMapper implements RowMapper { + + @Override + public TaxComponentHistoryData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { + final BigDecimal percentage = rs.getBigDecimal("historyPercentage"); + final LocalDate startDate = JdbcSupport.getLocalDate(rs, "historyStartDate"); + final LocalDate endDate = JdbcSupport.getLocalDate(rs, "historyEndDate"); + return new TaxComponentHistoryData(percentage, startDate, endDate); + } + + } + + private static final class TaxGroupMapper implements RowMapper { + + private final String schema; + private final TaxGroupMappingsDataMapper taxGroupMappingsDataMapper = new TaxGroupMappingsDataMapper(); + + public TaxGroupMapper() { + StringBuilder sb = new StringBuilder(); + sb.append("tg.id as id, tg.name as name,"); + sb.append("tgm.id as mappingId,"); + sb.append("tc.id as taxComponentId, tc.name as taxComponentName,"); + sb.append("tgm.start_date as startDate, tgm.end_date as endDate "); + sb.append(" from m_tax_group tg "); + sb.append(" inner join m_tax_group_mappings tgm on tgm.tax_group_id = tg.id "); + sb.append(" inner join m_tax_component tc on tc.id = tgm.tax_component_id "); + this.schema = sb.toString(); + } + + @Override + public TaxGroupData mapRow(ResultSet rs, int rowNum) throws SQLException { + final Long id = rs.getLong("id"); + final String name = rs.getString("name"); + final Collection taxAssociations = new ArrayList<>(); + taxAssociations.add(this.taxGroupMappingsDataMapper.mapRow(rs, rowNum)); + while (rs.next()) { + if (id.equals(rs.getLong("id"))) { + taxAssociations.add(this.taxGroupMappingsDataMapper.mapRow(rs, rowNum)); + } else { + rs.previous(); + break; + } + } + return TaxGroupData.instance(id, name, taxAssociations); + } + + public String getSchema() { + return this.schema; + } + + } + + private static final class TaxGroupMappingsDataMapper implements RowMapper { + + @Override + public TaxGroupMappingsData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { + final Long mappingId = rs.getLong("mappingId"); + final Long id = rs.getLong("taxComponentId"); + final String name = rs.getString("taxComponentName"); + TaxComponentData componentData = TaxComponentData.lookup(id, name); + + final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate"); + final LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate"); + return new TaxGroupMappingsData(mappingId, componentData, startDate, endDate); + } + + } + + private static final class TaxComponentLookUpMapper implements RowMapper { + + private final String schema; + + public TaxComponentLookUpMapper() { + StringBuilder sb = new StringBuilder(); + sb.append("tc.id as id, tc.name as name "); + sb.append(" from m_tax_component tc "); + this.schema = sb.toString(); + } + + public String getSchema() { + return this.schema; + } + + @Override + public TaxComponentData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { + final Long id = rs.getLong("id"); + final String name = rs.getString("name"); + return TaxComponentData.lookup(id, name); + } + + } + + private static final class TaxGroupLookUpMapper implements RowMapper { + + private final String schema; + + public TaxGroupLookUpMapper() { + StringBuilder sb = new StringBuilder(); + sb.append("tg.id as id, tg.name as name "); + sb.append(" from m_tax_group tg "); + this.schema = sb.toString(); + } + + public String getSchema() { + return this.schema; + } + + @Override + public TaxGroupData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { + final Long id = rs.getLong("id"); + final String name = rs.getString("name"); + return TaxGroupData.lookup(id, name); + } + + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxUtils.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxUtils.java new file mode 100644 index 000000000..02368df89 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxUtils.java @@ -0,0 +1,94 @@ +/** + * 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.tax.service; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; +import org.apache.fineract.portfolio.tax.domain.TaxComponent; +import org.apache.fineract.portfolio.tax.domain.TaxGroupMappings; +import org.joda.time.LocalDate; + +public class TaxUtils { + + public static Map splitTax(final BigDecimal amount, final LocalDate date, + final List taxGroupMappings, final int scale) { + Map map = new HashMap<>(3); + if (amount != null) { + final double amountVal = amount.doubleValue(); + double cent_percentage = Double.valueOf("100.0"); + for (TaxGroupMappings groupMappings : taxGroupMappings) { + if (groupMappings.occursOnDayFromAndUpToAndIncluding(date)) { + TaxComponent component = groupMappings.getTaxComponent(); + BigDecimal percentage = component.getApplicablePercentage(date); + if (percentage != null) { + double percentageVal = percentage.doubleValue(); + double tax = amountVal * percentageVal / cent_percentage; + map.put(component, BigDecimal.valueOf(tax).setScale(scale, MoneyHelper.getRoundingMode())); + } + } + } + } + return map; + } + + public static BigDecimal incomeAmount(final BigDecimal amount, final LocalDate date, final List taxGroupMappings, + final int scale) { + Map map = splitTax(amount, date, taxGroupMappings, scale); + return incomeAmount(amount, map); + } + + public static BigDecimal incomeAmount(final BigDecimal amount, final Map map) { + BigDecimal totalTax = totalTaxAmount(map); + return amount.subtract(totalTax); + } + + public static BigDecimal totalTaxAmount(final Map map) { + BigDecimal totalTax = BigDecimal.ZERO; + for (BigDecimal tax : map.values()) { + totalTax = totalTax.add(tax); + } + return totalTax; + } + + public static BigDecimal addTax(final BigDecimal amount, final LocalDate date, final List taxGroupMappings, + final int scale) { + BigDecimal totalAmount = null; + if (amount != null && amount.compareTo(BigDecimal.ZERO) == 1) { + double percentageVal = 0; + double amountVal = amount.doubleValue(); + double cent_percentage = Double.valueOf("100.0"); + for (TaxGroupMappings groupMappings : taxGroupMappings) { + if (groupMappings.occursOnDayFromAndUpToAndIncluding(date)) { + TaxComponent component = groupMappings.getTaxComponent(); + BigDecimal percentage = component.getApplicablePercentage(date); + if (percentage != null) { + percentageVal = percentageVal + percentage.doubleValue(); + } + } + } + double total = amountVal * cent_percentage / (cent_percentage - percentageVal); + totalAmount = BigDecimal.valueOf(total).setScale(scale, MoneyHelper.getRoundingMode()); + } + return totalAmount; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformService.java new file mode 100644 index 000000000..950b62b1f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformService.java @@ -0,0 +1,34 @@ +/** + * 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.tax.service; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; + +public interface TaxWritePlatformService { + + public CommandProcessingResult createTaxComponent(JsonCommand command); + + public CommandProcessingResult updateTaxComponent(final Long id, final JsonCommand command); + + public CommandProcessingResult createTaxGroup(JsonCommand command); + + public CommandProcessingResult updateTaxGroup(final Long id, final JsonCommand command); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformServiceImpl.java new file mode 100644 index 000000000..a96b57dbc --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/tax/service/TaxWritePlatformServiceImpl.java @@ -0,0 +1,111 @@ +/** + * 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.tax.service; + +import java.util.List; +import java.util.Map; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.portfolio.tax.domain.TaxComponent; +import org.apache.fineract.portfolio.tax.domain.TaxComponentRepository; +import org.apache.fineract.portfolio.tax.domain.TaxComponentRepositoryWrapper; +import org.apache.fineract.portfolio.tax.domain.TaxGroup; +import org.apache.fineract.portfolio.tax.domain.TaxGroupMappings; +import org.apache.fineract.portfolio.tax.domain.TaxGroupRepository; +import org.apache.fineract.portfolio.tax.domain.TaxGroupRepositoryWrapper; +import org.apache.fineract.portfolio.tax.serialization.TaxValidator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class TaxWritePlatformServiceImpl implements TaxWritePlatformService { + + private final TaxValidator validator; + private final TaxAssembler taxAssembler; + private final TaxComponentRepository taxComponentRepository; + private final TaxComponentRepositoryWrapper taxComponentRepositoryWrapper; + private final TaxGroupRepository taxGroupRepository; + private final TaxGroupRepositoryWrapper taxGroupRepositoryWrapper; + + @Autowired + public TaxWritePlatformServiceImpl(final TaxValidator validator, final TaxAssembler taxAssembler, + final TaxComponentRepository taxComponentRepository, final TaxGroupRepository taxGroupRepository, + final TaxComponentRepositoryWrapper taxComponentRepositoryWrapper, final TaxGroupRepositoryWrapper taxGroupRepositoryWrapper) { + this.validator = validator; + this.taxAssembler = taxAssembler; + this.taxComponentRepository = taxComponentRepository; + this.taxGroupRepository = taxGroupRepository; + this.taxComponentRepositoryWrapper = taxComponentRepositoryWrapper; + this.taxGroupRepositoryWrapper = taxGroupRepositoryWrapper; + } + + @Override + public CommandProcessingResult createTaxComponent(final JsonCommand command) { + this.validator.validateForTaxComponentCreate(command.json()); + TaxComponent taxComponent = this.taxAssembler.assembleTaxComponentFrom(command); + this.taxComponentRepository.save(taxComponent); + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(taxComponent.getId()) // + .build(); + } + + @Override + public CommandProcessingResult updateTaxComponent(final Long id, final JsonCommand command) { + this.validator.validateForTaxComponentUpdate(command.json()); + final TaxComponent taxComponent = this.taxComponentRepositoryWrapper.findOneWithNotFoundDetection(id); + this.validator.validateStartDate(taxComponent.startDate(), command); + Map changes = taxComponent.update(command); + this.validator.validateTaxComponentForUpdate(taxComponent); + this.taxComponentRepository.save(taxComponent); + return new CommandProcessingResultBuilder() // + .withEntityId(id) // + .with(changes).build(); + } + + @Override + public CommandProcessingResult createTaxGroup(final JsonCommand command) { + this.validator.validateForTaxGroupCreate(command.json()); + final TaxGroup taxGroup = this.taxAssembler.assembleTaxGroupFrom(command); + this.validator.validateTaxGroup(taxGroup); + this.taxGroupRepository.save(taxGroup); + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(taxGroup.getId()) // + .build(); + } + + @Override + public CommandProcessingResult updateTaxGroup(final Long id, final JsonCommand command) { + this.validator.validateForTaxGroupUpdate(command.json()); + final TaxGroup taxGroup = this.taxGroupRepositoryWrapper.findOneWithNotFoundDetection(id); + final boolean isUpdate = true; + List groupMappings = this.taxAssembler.assembleTaxGroupMappingsFrom(command, isUpdate); + this.validator.validateTaxGroupEndDateAndTaxComponent(taxGroup, groupMappings); + Map changes = taxGroup.update(command, groupMappings); + this.validator.validateTaxGroup(taxGroup); + this.taxGroupRepository.save(taxGroup); + return new CommandProcessingResultBuilder() // + .withEntityId(id) // + .with(changes).build(); + } + +} diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V298__savings_interest_tax.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V298__savings_interest_tax.sql new file mode 100644 index 000000000..1bc0854de --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V298__savings_interest_tax.sql @@ -0,0 +1,111 @@ +CREATE TABLE `m_tax_component` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `name` VARCHAR(50) NOT NULL, + `percentage` DECIMAL(19,6) NOT NULL, + `debit_account_type_enum` SMALLINT(2) NULL DEFAULT NULL, + `debit_account_id` BIGINT(20) NULL DEFAULT NULL, + `credit_account_type_enum` SMALLINT(2) NULL DEFAULT NULL, + `credit_account_id` BIGINT(20) NULL DEFAULT NULL, + `start_date` DATE NOT NULL, + `createdby_id` BIGINT(20) NOT NULL, + `created_date` DATETIME NOT NULL, + `lastmodifiedby_id` BIGINT(20) NOT NULL, + `lastmodified_date` DATETIME NOT NULL, + PRIMARY KEY (`id`), + INDEX `FK_tax_component_debit_gl_account` (`debit_account_id`), + INDEX `FK_tax_component_credit_gl_account` (`credit_account_id`), + INDEX `FK_tax_component_createdby` (`createdby_id`), + INDEX `FK_tax_component_lastmodifiedby` (`lastmodifiedby_id`), + CONSTRAINT `FK_tax_component_createdby` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`), + CONSTRAINT `FK_tax_component_credit_gl_account` FOREIGN KEY (`credit_account_id`) REFERENCES `acc_gl_account` (`id`), + CONSTRAINT `FK_tax_component_debit_gl_account` FOREIGN KEY (`debit_account_id`) REFERENCES `acc_gl_account` (`id`), + CONSTRAINT `FK_tax_component_lastmodifiedby` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`) +); + +CREATE TABLE `m_tax_component_history` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `tax_component_id` BIGINT(20) NOT NULL, + `percentage` DECIMAL(19,6) NOT NULL, + `start_date` DATE NOT NULL, + `end_date` DATE NOT NULL, + `createdby_id` BIGINT(20) NOT NULL, + `created_date` DATETIME NOT NULL, + `lastmodifiedby_id` BIGINT(20) NOT NULL, + `lastmodified_date` DATETIME NOT NULL, + PRIMARY KEY (`id`), + INDEX `FK_tax_component_history_tax_component_id` (`tax_component_id`), + INDEX `FK_tax_component_history_createdby` (`createdby_id`), + INDEX `FK_tax_component_history_lastmodifiedby` (`lastmodifiedby_id`), + CONSTRAINT `FK_tax_component_history_createdby` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`), + CONSTRAINT `FK_tax_component_history_lastmodifiedby` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`), + CONSTRAINT `FK_tax_component_history_tax_component_id` FOREIGN KEY (`tax_component_id`) REFERENCES `m_tax_component` (`id`) +); + +CREATE TABLE `m_tax_group` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `name` VARCHAR(50) NOT NULL, + `createdby_id` BIGINT(20) NOT NULL, + `created_date` DATETIME NOT NULL, + `lastmodifiedby_id` BIGINT(20) NOT NULL, + `lastmodified_date` DATETIME NOT NULL, + PRIMARY KEY (`id`), + INDEX `FK_tax_group_createdby` (`createdby_id`), + INDEX `FK_tax_group_lastmodifiedby` (`lastmodifiedby_id`), + CONSTRAINT `FK_tax_group_createdby` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`), + CONSTRAINT `FK_tax_group_lastmodifiedby` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`) +); + +CREATE TABLE `m_tax_group_mappings` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `tax_group_id` BIGINT(20) NOT NULL, + `tax_component_id` BIGINT(20) NOT NULL, + `start_date` DATE NOT NULL, + `end_date` DATE NULL DEFAULT NULL, + `createdby_id` BIGINT(20) NOT NULL, + `created_date` DATETIME NOT NULL, + `lastmodifiedby_id` BIGINT(20) NOT NULL, + `lastmodified_date` DATETIME NOT NULL, + PRIMARY KEY (`id`), + INDEX `FK_tax_group_mappings_tax_group` (`tax_group_id`), + INDEX `FK_tax_group_mappings_tax_component` (`tax_component_id`), + INDEX `FK_tax_group_mappings_createdby` (`createdby_id`), + INDEX `FK_tax_group_mappings_lastmodifiedby` (`lastmodifiedby_id`), + CONSTRAINT `FK_tax_group_mappings_createdby` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`), + CONSTRAINT `FK_tax_group_mappings_lastmodifiedby` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`), + CONSTRAINT `FK_tax_group_mappings_tax_component` FOREIGN KEY (`tax_component_id`) REFERENCES `m_tax_component` (`id`), + CONSTRAINT `FK_tax_group_mappings_tax_group` FOREIGN KEY (`tax_group_id`) REFERENCES `m_tax_group` (`id`) +); + +ALTER TABLE `m_charge` + ADD COLUMN `tax_group_id` BIGINT(20) NULL DEFAULT NULL, + ADD CONSTRAINT `FK_m_charge_m_tax_group` FOREIGN KEY (`tax_group_id`) REFERENCES `m_tax_group` (`id`); + +ALTER TABLE `m_savings_product` + ADD COLUMN `withhold_tax` TINYINT NOT NULL DEFAULT '0', + ADD COLUMN `tax_group_id` BIGINT NULL DEFAULT NULL, + ADD CONSTRAINT `FK_savings_product_tax_group` FOREIGN KEY (`tax_group_id`) REFERENCES `m_tax_group` (`id`); + +ALTER TABLE `m_savings_account` + ADD COLUMN `withhold_tax` TINYINT NOT NULL DEFAULT '0', + ADD COLUMN `tax_group_id` BIGINT NULL DEFAULT NULL, + ADD COLUMN `total_withhold_tax_derived` DECIMAL(19,6) NULL DEFAULT NULL AFTER `total_overdraft_interest_derived`, + ADD CONSTRAINT `FK_savings_account_tax_group` FOREIGN KEY (`tax_group_id`) REFERENCES `m_tax_group` (`id`); + +CREATE TABLE `m_savings_account_transaction_tax_details` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `savings_transaction_id` BIGINT(20) NOT NULL, + `tax_component_id` BIGINT(20) NOT NULL, + `amount` DECIMAL(19,6) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_savings_account_transaction_tax_details_savings_transaction` FOREIGN KEY (`savings_transaction_id`) REFERENCES `m_savings_account_transaction` (`id`), + CONSTRAINT `FK_savings_account_transaction_tax_details_tax_component` FOREIGN KEY (`tax_component_id`) REFERENCES `m_tax_component` (`id`) +); + + + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'READ_TAXCOMPONENT', 'TAXCOMPONENT', 'READ', 0),('organisation', 'CREATE_TAXCOMPONENT', 'TAXCOMPONENT', 'CREATE', 0),('organisation', 'CREATE_TAXCOMPONENT_CHECKER', 'TAXCOMPONENT', 'CREATE_CHECKER', 0), ('organisation', 'UPDATE_TAXCOMPONENT', 'TAXCOMPONENT', 'UPDATE', 0),('organisation', 'UPDATE_TAXCOMPONENT_CHECKER', 'TAXCOMPONENT', 'UPDATE_CHECKER', 0),('organisation', 'READ_TAXGROUP', 'TAXGROUP', 'READ', 0),('organisation', 'CREATE_TAXGROUP', 'TAXGROUP', 'CREATE', 0),('organisation', 'CREATE_TAXGROUP_CHECKER', 'TAXGROUP', 'CREATE_CHECKER', 0), ('organisation', 'UPDATE_TAXGROUP', 'TAXGROUP', 'UPDATE', 0),('organisation', 'UPDATE_TAXGROUP_CHECKER', 'TAXGROUP', 'UPDATE_CHECKER', 0),('portfolio', 'UPDATEWITHHOLDTAX_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UPDATEWITHHOLDTAX', 0),('portfolio', 'UPDATEWITHHOLDTAX_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UPDATEWITHHOLDTAX_CHECKER', 0); + + + + +