FINERACT-33 : savings withhold tax changes

This commit is contained in:
pramod
2016-04-11 14:44:59 +05:30
parent 446ff4403f
commit 3015747f3d
103 changed files with 5490 additions and 491 deletions
@@ -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) {
@@ -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
@@ -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
@@ -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<String, String> 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<String, String> getBasicTaxComponentMap(final String percentage) {
final HashMap<String, String> 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);
}
}
@@ -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<Integer> 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<Integer> taxComponentIds) {
final HashMap<String, Object> 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<HashMap<String, String>> getTaxGroupComponents(final Collection<Integer> taxComponentIds) {
List<HashMap<String, String>> taxGroupComponents = new ArrayList<>();
for (Integer taxComponentId : taxComponentIds) {
taxGroupComponents.add(getTaxComponentMap(taxComponentId));
}
return taxGroupComponents;
}
public static HashMap<String, String> getTaxComponentMap(final Integer taxComponentId) {
final HashMap<String, String> 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);
}
}
@@ -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<HashMap> 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);
}
}
@@ -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);
@@ -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<HashMap<String, String>> 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<String, Object> 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<String, String> getAccountMappingForCashBased() {
final Map<String, String> map = new HashMap<>();
@@ -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);
@@ -98,6 +98,8 @@ public class RecurringDepositProductHelper {
private Account[] accountList = null;
private List<HashMap<String, String>> 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<String, Object> 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<String, String> getAccountMappingForCashBased() {
final Map<String, String> map = new HashMap<>();
@@ -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<String, Object> 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<HashMap> 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);
}
@@ -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<String, String> 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<String, String> getAccountMappingForCashBased() {
final Map<String, String> map = new HashMap<>();
if (accountList != null) {
@@ -41,13 +41,14 @@ public class SavingsTransactionDTO {
/** Breakdowns of fees and penalties this Transaction pays **/
private final List<ChargePaymentDTO> penaltyPayments;
private final List<ChargePaymentDTO> feePayments;
private final List<TaxPaymentDTO> 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<ChargePaymentDTO> feePayments, final List<ChargePaymentDTO> penaltyPayments, final BigDecimal overdraftAmount,
boolean isAccountTransfer) {
boolean isAccountTransfer, final List<TaxPaymentDTO> 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<TaxPaymentDTO> getTaxPayments() {
return this.taxPayments;
}
}
@@ -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;
}
}
@@ -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<TaxPaymentDTO> taxPayments = new ArrayList<>();
if (map.containsKey("taxDetails")) {
@SuppressWarnings("unchecked")
final List<Map<String, Object>> taxDatas = (List<Map<String, Object>>) map.get("taxDetails");
for (final Map<String, Object> 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<TaxPaymentDTO> 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 {
@@ -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?
@@ -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;
}
}
@@ -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";
}
@@ -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<ChargeData>, Serializable {
private final BigDecimal maxCap;
private final EnumOptionData feeFrequency;
private final GLAccountData incomeOrLiabilityAccount;
private final TaxGroupData taxGroup;
private final Collection<CurrencyData> currencyOptions;
private final List<EnumOptionData> chargeCalculationTypeOptions;//
@@ -70,6 +72,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
private final List<EnumOptionData> feeFrequencyOptions;
private final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions;
private final Collection<TaxGroupData> taxGroupOptions;
public static ChargeData template(final Collection<CurrencyData> currencyOptions,
final List<EnumOptionData> chargeCalculationTypeOptions, final List<EnumOptionData> chargeAppliesToOptions,
@@ -77,31 +80,33 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final List<EnumOptionData> loansChargeCalculationTypeOptions, final List<EnumOptionData> loansChargeTimeTypeOptions,
final List<EnumOptionData> savingsChargeCalculationTypeOptions, final List<EnumOptionData> savingsChargeTimeTypeOptions,
final List<EnumOptionData> clientChargeCalculationTypeOptions, final List<EnumOptionData> clientChargeTimeTypeOptions,
final List<EnumOptionData> feeFrequencyOptions, final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions) {
final List<EnumOptionData> feeFrequencyOptions, final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions,
final Collection<TaxGroupData> 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<CurrencyData> currencyOptions = null;
final List<EnumOptionData> chargeCalculationTypeOptions = null;
@@ -116,13 +121,14 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final List<EnumOptionData> clientChargeCalculationTypeOptions = null;
final List<EnumOptionData> clientChargeTimeTypeOptions = null;
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = null;
final Collection<TaxGroupData> 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<ChargeData>, Serializable {
final List<EnumOptionData> feeFrequencyOptions = null;
final GLAccountData account = null;
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = null;
final TaxGroupData taxGroupData = null;
final Collection<TaxGroupData> 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<CurrencyData> currencyOptions, final List<EnumOptionData> chargeCalculationTypeOptions,
final List<EnumOptionData> chargeAppliesToOptions, final List<EnumOptionData> chargeTimeTypeOptions,
final List<EnumOptionData> chargePaymentModeOptions, final List<EnumOptionData> loansChargeCalculationTypeOptions,
@@ -172,7 +180,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final List<EnumOptionData> clientChargeTimeTypeOptions, final MonthDay feeOnMonthDay, final Integer feeInterval,
final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency,
final List<EnumOptionData> feeFrequencyOptions, final GLAccountData account,
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions) {
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions, final Collection<TaxGroupData> taxGroupOptions) {
this.id = id;
this.name = name;
this.amount = amount;
@@ -202,6 +210,8 @@ public class ChargeData implements Comparable<ChargeData>, 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<ChargeData>, 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() {
@@ -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<Long> {
@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<Long> {
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<Long> {
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<Long> {
}
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<Long> {
this.penalty = penalty;
this.active = active;
this.account = account;
this.taxGroup = taxGroup;
this.chargePaymentMode = paymentMode == null ? null : paymentMode.getValue();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
@@ -185,8 +192,8 @@ public class Charge extends AbstractPersistable<Long> {
} 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<Long> {
}
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<Long> {
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<Long> {
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;
}
}
@@ -56,7 +56,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
private final Set<String> 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);
}
@@ -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<EnumOptionData> feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = this.accountingDropdownReadPlatformService
.retrieveAccountMappingOptionsForCharges();
final Collection<TaxGroupData> 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);
}
}
@@ -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<String, Object> 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();
@@ -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<EnumOptionData> feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
// this field is applicable only for client charges
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = null;
final Collection<TaxGroupData> taxGroupOptions = null;
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null,
loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeFrequencyOptions,
incomeOrLiabilityAccountOptions);
incomeOrLiabilityAccountOptions, taxGroupOptions);
}
@Override
@@ -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<String> 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<String> FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = fixedDepositAccountRequestData();
public static final Set<String> FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = fixedDepositAccountResponseData();
@@ -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() {
@@ -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<String> 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
@@ -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<ChargeData> penaltyOptions = this.chargeReadPlatformService.retrieveSavingsApplicablePenalties();
penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions;
final Collection<TaxGroupData> 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;
@@ -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<TaxGroupData> 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;
@@ -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);
@@ -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<ChargeData> penaltyOptions = this.chargeReadPlatformService.retrieveSavingsApplicablePenalties();
penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions;
final Collection<TaxGroupData> 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;
@@ -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<DepositProductData> productOptions = null;
final Collection<StaffData> 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<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<SavingsAccountChargeData> charges, final Collection<ChargeData> 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() {
@@ -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<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
@@ -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<ChargeData> chargeOptions;
protected final Collection<ChargeData> penaltyOptions;
protected final InterestRateChartData chartTemplate;
protected final Collection<TaxGroupData> 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<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate) {
final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate,
final Collection<TaxGroupData> taxGroupOptions) {
final Long id = null;
final String name = null;
@@ -109,6 +114,8 @@ public class DepositProductData {
final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
final Collection<InterestRateChartData> 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<ChargeData> 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<CurrencyData> currencyOptions,
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
@@ -147,7 +159,7 @@ public class DepositProductData {
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate) {
final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate, Collection<TaxGroupData> 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<String, List<GLAccountData>> accountingMappingOptions = null;
final Collection<ChargeData> chargeOptions = null;
final Collection<ChargeData> penaltyOptions = null;
final Collection<TaxGroupData> 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<String, Object> accountingMappings = null;
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
@@ -221,6 +235,7 @@ public class DepositProductData {
final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
final Collection<InterestRateChartData> interestRateCharts = null;
final InterestRateChartData chartTemplate = null;
final Collection<TaxGroupData> 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<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
final Collection<InterestRateChartData> interestRateCharts = null;
final InterestRateChartData chartTemplate = null;
final boolean withHoldTax = false;
final TaxGroupData taxGroup = null;
final Collection<TaxGroupData> 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<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings,
final Collection<InterestRateChartData> interestRateCharts, final InterestRateChartData chartTemplate,
final BigDecimal minBalanceForInterestCalculation) {
final BigDecimal minBalanceForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup,
final Collection<TaxGroupData> 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<InterestRateChartData> interestRateCharts) {
@@ -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();
}
}
}
@@ -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<SavingsAccountTransactionData> transactions = null;
final boolean withHoldTax = false;
final TaxGroupData taxGroup = null;
final Collection<DepositProductData> productOptions = null;
final Collection<StaffData> 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<EnumOptionData> periodFrequencyTypeOptions, final EnumOptionData depositType,
final EnumOptionData onAccountClosure, final Collection<EnumOptionData> onAccountClosureOptions,
final Collection<PaymentTypeData> paymentTypeOptions, final Collection<SavingsAccountData> 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;
@@ -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<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate,
final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
final Collection<EnumOptionData> periodFrequencyTypeOptions) {
final Collection<EnumOptionData> periodFrequencyTypeOptions, final Collection<TaxGroupData> 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<ChargeData> 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<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
@@ -146,7 +153,7 @@ public class FixedDepositProductData extends DepositProductData {
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate,
final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
final Collection<EnumOptionData> periodFrequencyTypeOptions) {
final Collection<EnumOptionData> periodFrequencyTypeOptions, final Collection<TaxGroupData> 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<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
final Collection<TaxGroupData> 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<EnumOptionData> periodFrequencyTypeOptions = null;
final boolean withHoldTax = false;
final TaxGroupData taxGroup = null;
final Collection<TaxGroupData> 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<EnumOptionData> periodFrequencyTypeOptions) {
final BigDecimal maxDepositAmount, final Collection<EnumOptionData> periodFrequencyTypeOptions, final boolean withHoldTax,
final TaxGroupData taxGroup, final Collection<TaxGroupData> 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;
@@ -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<SavingsAccountData> 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;
@@ -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<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate,
final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
final Collection<EnumOptionData> periodFrequencyTypeOptions) {
final Collection<EnumOptionData> periodFrequencyTypeOptions, final Collection<TaxGroupData> 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<CurrencyData> currencyOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
@@ -154,7 +161,7 @@ public class RecurringDepositProductData extends DepositProductData {
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions, final InterestRateChartData chartTemplate,
final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
final Collection<EnumOptionData> periodFrequencyTypeOptions) {
final Collection<EnumOptionData> periodFrequencyTypeOptions, final Collection<TaxGroupData> 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<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
final Collection<TaxGroupData> 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<TaxGroupData> 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<EnumOptionData> 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<TaxGroupData> 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;
@@ -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<SavingsProductData> productOptions = null;
final Collection<StaffData> 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<SavingsAccountChargeData> charges = null;
final Collection<ChargeData> 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<SavingsAccountChargeData> charges = null;
final Collection<ChargeData> 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<SavingsAccountTransactionData> 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<SavingsAccountChargeData> charges, final Collection<ChargeData> 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() {
@@ -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);
@@ -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() {
@@ -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;
}
}
@@ -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;
}
}
@@ -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<String, List<GLAccountData>> accountingMappingOptions;
private final Collection<ChargeData> chargeOptions;
private final Collection<ChargeData> penaltyOptions;
private final BigDecimal nominalAnnualInterestRateOverdraft;
private final BigDecimal minOverdraftForInterestCalculation;
private final Collection<TaxGroupData> 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<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions) {
final Collection<ChargeData> penaltyOptions, final Collection<TaxGroupData> 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<ChargeData> 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<CurrencyData> currencyOptions,
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
@@ -156,7 +166,7 @@ public class SavingsProductData {
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<PaymentTypeData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions,
final Collection<ChargeData> penaltyOptions) {
final Collection<ChargeData> penaltyOptions, Collection<TaxGroupData> 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<String, Object> accountingMappings = null;
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
@@ -236,6 +248,7 @@ public class SavingsProductData {
final Collection<ChargeData> charges = null;
final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
final Collection<TaxGroupData> 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<CurrencyData> currencyOptions = null;
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions = null;
@@ -288,6 +304,7 @@ public class SavingsProductData {
final Collection<ChargeData> penaltyOptions = null;
final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
final Collection<TaxGroupData> 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<ChargeToGLAccountMapper> feeToIncomeAccountMappings,
final Collection<ChargeToGLAccountMapper> 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<TaxGroupData> 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() {
@@ -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();
}
}
@@ -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);
@@ -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<Long> existingTransactionIds = new HashSet<>();
final Set<Long> 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<Long> existingTransactionIds = new HashSet<>();
final Set<Long> 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<Long> existingTransactionIds = new HashSet<>();
final Set<Long> 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<Long> existingTransactionIds = new HashSet<>();
final Set<Long> 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<Long> existingTransactionIds, final SavingsAccount savingsAccount) {
List<SavingsAccountTransaction> 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;
}
}
}
}
@@ -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;
}
}
@@ -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<SavingsAccountCharge> 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<SavingsAccountCharge> 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);
@@ -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<Charge> charges, final DepositProductTermAndPreClosure productTermAndPreClosure, final Set<InterestRateChart> 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<Charge> charges,
final DepositProductTermAndPreClosure productTermAndPreClosure, final Set<InterestRateChart> 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);
}
}
@@ -106,7 +106,7 @@ public class RecurringDepositAccount extends SavingsAccount {
final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> 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<SavingsAccountCharge> 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<SavingsAccountCharge> 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);
@@ -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<Charge> charges, final DepositProductTermAndPreClosure productTermAndPreClosure,
final DepositProductRecurringDetail recurringDetail, final Set<InterestRateChart> 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<Charge> charges,
final DepositProductTermAndPreClosure productTermAndPreClosure, final DepositProductRecurringDetail recurringDetail,
final Set<InterestRateChart> 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;
}
@@ -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<Long> {
@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<Long> {
final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> 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<Long> {
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> 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<Long> {
final boolean withdrawalFeeApplicableForTransfer, final Set<SavingsAccountCharge> 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<Long> {
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<Long> {
Money interestPostedToDate = Money.zero(this.currency);
boolean recalucateDailyBalanceDetails = false;
boolean applyWithHoldTax = isWithHoldTaxApplicableForInterestPosting();
final List<SavingsAccountTransaction> withholdTransactions = new ArrayList<>();
withholdTransactions.addAll(findWithHoldTransactions());
for (final PostingPeriod interestPostingPeriod : postingPeriods) {
@@ -479,6 +496,9 @@ public class SavingsAccount extends AbstractPersistable<Long> {
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<Long> {
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<Long> {
interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated());
}
this.transactions.add(newPostingTransaction);
if (applyWithHoldTaxForOldTransaction) {
createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate);
}
recalucateDailyBalanceDetails = true;
}
}
@@ -518,10 +548,22 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
}
protected List<SavingsAccountTransaction> findWithHoldTransactions() {
final List<SavingsAccountTransaction> 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<Long> {
break;
}
}
return postingTransation;
}
protected SavingsAccountTransaction findTransactionFor(final LocalDate postingDate, final List<SavingsAccountTransaction> 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<TaxComponent, BigDecimal> 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<TaxComponent, BigDecimal> 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<Long> {
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<Long> {
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<SavingsAccountTransaction> 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;
}
}
@@ -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<SavingsAccountCharge> 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);
@@ -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<PostingPeriod> allPostingPeriods) {
@@ -122,4 +127,8 @@ public final class SavingsAccountSummary {
return this.accountBalance;
}
public BigDecimal getTotalInterestPosted() {
return this.totalInterestPosted;
}
}
@@ -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<Long> {
@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<SavingsAccountTransactionTaxDetails> taxDetails = new ArrayList<>();
protected SavingsAccountTransaction() {
this.dateOf = null;
this.typeOf = null;
@@ -142,14 +149,14 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
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<Long> {
.getAccountBalance(), isReversed, appUser);
}
public static SavingsAccountTransaction withHoldTax(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
final Money amount, final Map<TaxComponent, BigDecimal> 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<TaxComponent, BigDecimal> taxDetails, final SavingsAccountTransaction accountTransaction) {
if (taxDetails != null) {
for (Map.Entry<TaxComponent, BigDecimal> 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<Long> {
thisTransactionData.put("savingsChargesPaid", savingsChargesPaidData);
}
if (!this.taxDetails.isEmpty()) {
final List<Map<String, Object>> taxData = new ArrayList<>();
for (final SavingsAccountTransactionTaxDetails taxDetails : this.taxDetails) {
final Map<String, Object> 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<Long> {
}
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<Long> {
return this.amount;
}
public List<SavingsAccountTransactionTaxDetails> getTaxDetails() {
return this.taxDetails;
}
public void updateAmount(final Money amount) {
this.amount = amount.getAmount();
}
}
@@ -122,8 +122,7 @@ public final class SavingsAccountTransactionSummaryWrapper {
return total.getAmountDefaultedToNullIfZero();
}
public BigDecimal calculateTotalOverdraftInterest(MonetaryCurrency currency,
List<SavingsAccountTransaction> transactions) {
public BigDecimal calculateTotalOverdraftInterest(MonetaryCurrency currency, List<SavingsAccountTransaction> 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<SavingsAccountTransaction> transactions) {
Money total = Money.zero(currency);
for (final SavingsAccountTransaction transaction : transactions) {
if (transaction.isWithHoldTaxAndNotReversed()) {
total = total.plus(transaction.getAmount(currency));
}
}
return total.getAmountDefaultedToNullIfZero();
}
}
@@ -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<Long> {
@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();
}
}
@@ -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<Long> {
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<Charge> charges;
@Column(name = "allow_overdraft")
@@ -165,12 +169,12 @@ public class SavingsProduct extends AbstractPersistable<Long> {
@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<Long> {
@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<Long> {
final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set<Charge> 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<Long> {
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set<Charge> 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<Long> {
final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set<Charge> 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<Long> {
this.enforceMinRequiredBalance = enforceMinRequiredBalance;
this.minRequiredBalance = minRequiredBalance;
this.minBalanceForInterestCalculation = minBalanceForInterestCalculation;
this.withHoldTax = withHoldTax;
this.taxGroup = taxGroup;
}
/**
@@ -276,9 +292,11 @@ public class SavingsProduct extends AbstractPersistable<Long> {
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<Long> {
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<Long> {
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<Long> {
}
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;
}
}
@@ -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<Charge> 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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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();
}
@@ -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<ApiParameterError> 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<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService
@@ -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<ApiParameterError> 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<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService
@@ -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<EnumOptionData> feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
// this field is applicable only for client charges
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = null;
final Collection<TaxGroupData> 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
@@ -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<SavingsAccountChargeData> charges = fromChargesToSavingsCharges(productCharges);
final boolean feeChargesOnly = false;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsProductApplicableCharges(feeChargesOnly);
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService
.retrieveSavingsProductApplicableCharges(feeChargesOnly);
Collection<StaffData> fieldOfficerOptions = null;
@@ -598,7 +617,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final Collection<SavingsAccountChargeData> charges = null;
final boolean feeChargesOnly = true;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsProductApplicableCharges(feeChargesOnly);
final Collection<ChargeData> 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);
}
}
@@ -79,4 +79,6 @@ public interface SavingsAccountWritePlatformService {
Set<Long> existingReversedTransactionIds);
void postInterest(SavingsAccount account);
CommandProcessingResult modifyWithHoldTax(Long savingsAccountId, JsonCommand command);
}
@@ -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<DepositAccountOnHoldTransaction> 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<Long> existingTransactionIds = new HashSet<>();
final Set<Long> 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<DepositAccountOnHoldTransaction> 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<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
@@ -553,10 +551,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
financialYearBeginningMonth);
}
List<DepositAccountOnHoldTransaction> 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<DepositAccountOnHoldTransaction> 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<ApiParameterError> 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<DepositAccountOnHoldTransaction> 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<String, Object> 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<ApiParameterError> 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) {
@@ -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;
@@ -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<SavingsProductData> 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);
}
@@ -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<ApiParameterError> 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<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService
@@ -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";
}
@@ -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<TaxComponentData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired
public TaxComponentApiResource(final PlatformSecurityContext context, final TaxReadPlatformService readPlatformService,
final DefaultToApiJsonSerializer<TaxComponentData> 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<TaxComponentData> 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);
}
}
@@ -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<TaxGroupData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired
public TaxGroupApiResource(final PlatformSecurityContext context, final TaxReadPlatformService readPlatformService,
final DefaultToApiJsonSerializer<TaxGroupData> 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<TaxGroupData> 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);
}
}
@@ -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<TaxComponentHistoryData> taxComponentHistories;
// template options
@SuppressWarnings("unused")
private final Map<String, List<GLAccountData>> glAccountOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> 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<TaxComponentHistoryData> taxComponentHistories) {
final Map<String, List<GLAccountData>> glAccountOptions = null;
final Collection<EnumOptionData> 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<TaxComponentHistoryData> taxComponentHistories = null;
final Map<String, List<GLAccountData>> glAccountOptions = null;
final Collection<EnumOptionData> glAccountTypeOptions = null;
return new TaxComponentData(id, name, percentage, debitAccountType, debitAcount, creditAccountType, creditAcount, startDate,
taxComponentHistories, glAccountOptions, glAccountTypeOptions);
}
public static TaxComponentData template(final Map<String, List<GLAccountData>> glAccountOptions,
final Collection<EnumOptionData> 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<TaxComponentHistoryData> 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<TaxComponentHistoryData> taxComponentHistories,
final Map<String, List<GLAccountData>> glAccountOptions, final Collection<EnumOptionData> 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;
}
}
@@ -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;
}
}
@@ -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<TaxGroupMappingsData> taxAssociations;
// Template options
@SuppressWarnings("unused")
private final Collection<TaxComponentData> taxComponents;
public static TaxGroupData instance(final Long id, final String name, final Collection<TaxGroupMappingsData> taxAssociations) {
final Collection<TaxComponentData> taxComponents = null;
return new TaxGroupData(id, name, taxAssociations, taxComponents);
}
public static TaxGroupData lookup(final Long id, final String name) {
final Collection<TaxComponentData> taxComponents = null;
final Collection<TaxGroupMappingsData> taxAssociations = null;
return new TaxGroupData(id, name, taxAssociations, taxComponents);
}
public static TaxGroupData template(final Collection<TaxComponentData> taxComponents) {
final Long id = null;
final String name = null;
final Collection<TaxGroupMappingsData> taxAssociations = null;
return new TaxGroupData(id, name, taxAssociations, taxComponents);
}
public static TaxGroupData template(final TaxGroupData taxGroupData, final Collection<TaxComponentData> taxComponents) {
return new TaxGroupData(taxGroupData.id, taxGroupData.name, taxGroupData.taxAssociations, taxComponents);
}
private TaxGroupData(final Long id, final String name, final Collection<TaxGroupMappingsData> taxAssociations,
final Collection<TaxComponentData> taxComponents) {
this.id = id;
this.name = name;
this.taxAssociations = taxAssociations;
this.taxComponents = taxComponents;
}
}
@@ -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;
}
}
@@ -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<AppUser, Long> {
@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<TaxComponentHistory> taxComponentHistories = new ArrayList<>();
@LazyCollection(LazyCollectionOption.TRUE)
@OneToMany(cascade = CascadeType.DETACH, mappedBy = "taxComponent", orphanRemoval = false)
private List<TaxGroupMappings> 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<String, Object> update(final JsonCommand command) {
final Map<String, Object> 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<String, Object> 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<TaxComponentHistory> getTaxComponentHistories() {
return this.taxComponentHistories;
}
public List<TaxGroupMappings> getTaxGroupMappings() {
return this.taxGroupMappings;
}
public Collection<LocalDate> allStartDates() {
List<LocalDate> 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;
}
}
@@ -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<AppUser, Long> {
@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;
}
}
@@ -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<TaxComponent, Long>, JpaSpecificationExecutor<TaxComponent> {
}
@@ -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;
}
}
@@ -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<AppUser, Long> {
@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> taxGroupMappings = new ArrayList<>();
protected TaxGroup() {
}
private TaxGroup(final String name, final List<TaxGroupMappings> taxGroupMappings) {
this.name = name;
this.taxGroupMappings = taxGroupMappings;
}
public static TaxGroup createTaxGroup(final String name, final List<TaxGroupMappings> taxGroupMappings) {
return new TaxGroup(name, taxGroupMappings);
}
public Map<String, Object> update(final JsonCommand command, final List<TaxGroupMappings> taxGroupMappings) {
final Map<String, Object> 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<Long> taxComponentList = new ArrayList<>();
final List<Map<String, Object>> 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<TaxGroupMappings> getTaxGroupMappings() {
return this.taxGroupMappings;
}
public String getName() {
return this.name;
}
}
@@ -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<AppUser, Long> {
@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<Map<String, Object>> changes) {
if (endDate != null && this.endDate == null) {
this.endDate = endDate;
Map<String, Object> 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;
}
}
@@ -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<TaxGroup, Long>, JpaSpecificationExecutor<TaxGroup> {
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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<String> supportedTaxComponentCreateParameters = new HashSet<>(Arrays.asList("dateFormat", "locale",
TaxApiConstants.nameParamName, TaxApiConstants.percentageParamName, TaxApiConstants.startDateParamName,
TaxApiConstants.debitAccountTypeParamName, TaxApiConstants.debitAcountIdParamName, TaxApiConstants.creditAccountTypeParamName,
TaxApiConstants.creditAcountIdParamName));
final Set<String> supportedTaxComponentUpdateParameters = new HashSet<>(Arrays.asList("dateFormat", "locale",
TaxApiConstants.nameParamName, TaxApiConstants.percentageParamName, TaxApiConstants.startDateParamName));
final Set<String> supportedTaxGroupParameters = new HashSet<>(Arrays.asList("dateFormat", "locale", TaxApiConstants.nameParamName,
TaxApiConstants.taxComponentsParamName));
final Set<String> supportedTaxGroupTaxComponentsCreateParameters = new HashSet<>(Arrays.asList(TaxApiConstants.taxComponentIdParamName,
TaxApiConstants.startDateParamName));
final Set<String> 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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedTaxComponentCreateParameters);
final List<ApiParameterError> 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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedTaxComponentUpdateParameters);
final List<ApiParameterError> 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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedTaxGroupParameters);
final List<ApiParameterError> 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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedTaxGroupParameters);
final List<ApiParameterError> 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<TaxGroupMappings> groupMappings) {
final List<ApiParameterError> 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<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("tax.group");
List<TaxGroupMappings> groupMappings = taxGroup.getTaxGroupMappings();
validateGroupTotal(groupMappings, baseDataValidator, "total.percentage");
validateOverlappingComponents(groupMappings, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateTaxComponentForUpdate(final TaxComponent taxComponent) {
final List<ApiParameterError> 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<ApiParameterError> 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<TaxGroupMappings> taxMappings, final DataValidatorBuilder baseDataValidator) {
for (TaxGroupMappings groupMappingsOne : taxMappings) {
final List<TaxGroupMappings> 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<TaxGroupMappings> taxMappings, final DataValidatorBuilder baseDataValidator,
final String paramenter) {
for (TaxGroupMappings groupMappingsOne : taxMappings) {
Collection<LocalDate> 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<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
@@ -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<ApiParameterError> 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<TaxGroupMappings> groupMappings = assembleTaxGroupMappingsFrom(command, isUpdate);
return TaxGroup.createTaxGroup(name, groupMappings);
}
public List<TaxGroupMappings> assembleTaxGroupMappingsFrom(final JsonCommand command, boolean isUpdate) {
List<TaxGroupMappings> 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<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
@@ -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<TaxComponentData> retrieveAllTaxComponents();
Collection<TaxGroupData> retrieveAllTaxGroups();
Collection<TaxGroupData> retrieveTaxGroupsForLookUp();
}
@@ -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<TaxComponentData> 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<TaxGroupData> 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<TaxComponentData> retrieveTaxComponentsForLookUp() {
String sql = "select " + this.taxComponentLookUpMapper.getSchema();
return this.jdbcTemplate.query(sql, this.taxComponentLookUpMapper);
}
@Override
public Collection<TaxGroupData> retrieveTaxGroupsForLookUp() {
String sql = "select " + this.taxGroupLookUpMapper.getSchema();
return this.jdbcTemplate.query(sql, this.taxGroupLookUpMapper);
}
private static final class TaxComponentMapper implements RowMapper<TaxComponentData> {
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<TaxComponentHistoryData> 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<TaxComponentHistoryData> {
@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<TaxGroupData> {
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<TaxGroupMappingsData> 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<TaxGroupMappingsData> {
@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<TaxComponentData> {
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<TaxGroupData> {
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);
}
}
}
@@ -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<TaxComponent, BigDecimal> splitTax(final BigDecimal amount, final LocalDate date,
final List<TaxGroupMappings> taxGroupMappings, final int scale) {
Map<TaxComponent, BigDecimal> 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> taxGroupMappings,
final int scale) {
Map<TaxComponent, BigDecimal> map = splitTax(amount, date, taxGroupMappings, scale);
return incomeAmount(amount, map);
}
public static BigDecimal incomeAmount(final BigDecimal amount, final Map<TaxComponent, BigDecimal> map) {
BigDecimal totalTax = totalTaxAmount(map);
return amount.subtract(totalTax);
}
public static BigDecimal totalTaxAmount(final Map<TaxComponent, BigDecimal> 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> 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;
}
}

Some files were not shown because too many files have changed in this diff Show More