Dormant Savings Feature

This commit is contained in:
Adi Narayana Raju
2016-05-02 19:21:37 +05:30
parent a2d794a34d
commit 9d4d90121a
36 changed files with 911 additions and 130 deletions
@@ -151,7 +151,7 @@ public class AccountingConstants {
/*** Accounting placeholders for cash based accounting for savings products ***/ /*** Accounting placeholders for cash based accounting for savings products ***/
public static enum CASH_ACCOUNTS_FOR_SAVINGS { public static enum CASH_ACCOUNTS_FOR_SAVINGS {
SAVINGS_REFERENCE(1), SAVINGS_CONTROL(2), INTEREST_ON_SAVINGS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), TRANSFERS_SUSPENSE( SAVINGS_REFERENCE(1), SAVINGS_CONTROL(2), INTEREST_ON_SAVINGS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), TRANSFERS_SUSPENSE(
10), OVERDRAFT_PORTFOLIO_CONTROL(11), INCOME_FROM_INTEREST(12), LOSSES_WRITTEN_OFF(13); 10), OVERDRAFT_PORTFOLIO_CONTROL(11), INCOME_FROM_INTEREST(12), LOSSES_WRITTEN_OFF(13), ESCHEAT_LIABILITY(14);
private final Integer value; private final Integer value;
@@ -192,7 +192,7 @@ public class AccountingConstants {
"paymentTypeId"), FUND_SOURCE("fundSourceAccountId"), TRANSFERS_SUSPENSE("transfersInSuspenseAccountId"), FEE_INCOME_ACCOUNT_MAPPING( "paymentTypeId"), FUND_SOURCE("fundSourceAccountId"), TRANSFERS_SUSPENSE("transfersInSuspenseAccountId"), FEE_INCOME_ACCOUNT_MAPPING(
"feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID( "feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING("penaltyToIncomeAccountMappings"), CHARGE_ID("chargeId"), INCOME_ACCOUNT_ID(
"incomeAccountId"), OVERDRAFT_PORTFOLIO_CONTROL("overdraftPortfolioControlId"), INCOME_FROM_INTEREST("incomeFromInterestId"), LOSSES_WRITTEN_OFF( "incomeAccountId"), OVERDRAFT_PORTFOLIO_CONTROL("overdraftPortfolioControlId"), INCOME_FROM_INTEREST("incomeFromInterestId"), LOSSES_WRITTEN_OFF(
"writeOffAccountId"); "writeOffAccountId"), ESCHEAT_LIABILITY("escheatLiabilityId");
private final String value; private final String value;
@@ -215,7 +215,8 @@ public class AccountingConstants {
"incomeFromPenaltyAccount"), INTEREST_ON_SAVINGS("interestOnSavingsAccount"), PAYMENT_TYPE("paymentType"), FUND_SOURCE( "incomeFromPenaltyAccount"), INTEREST_ON_SAVINGS("interestOnSavingsAccount"), PAYMENT_TYPE("paymentType"), FUND_SOURCE(
"fundSourceAccount"), TRANSFERS_SUSPENSE("transfersInSuspenseAccount"), PENALTY_INCOME_ACCOUNT_MAPPING( "fundSourceAccount"), TRANSFERS_SUSPENSE("transfersInSuspenseAccount"), PENALTY_INCOME_ACCOUNT_MAPPING(
"penaltyToIncomeAccountMappings"), CHARGE_ID("charge"), INCOME_ACCOUNT_ID("incomeAccount"), OVERDRAFT_PORTFOLIO_CONTROL( "penaltyToIncomeAccountMappings"), CHARGE_ID("charge"), INCOME_ACCOUNT_ID("incomeAccount"), OVERDRAFT_PORTFOLIO_CONTROL(
"overdraftPortfolioControl"), INCOME_FROM_INTEREST("incomeFromInterest"), LOSSES_WRITTEN_OFF("writeOffAccount"); "overdraftPortfolioControl"), INCOME_FROM_INTEREST("incomeFromInterest"), LOSSES_WRITTEN_OFF("writeOffAccount"),
ESCHEAT_LIABILITY("escheatLiabilityAccount");
private final String value; private final String value;
@@ -143,6 +143,11 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess
} }
} }
else if (savingsTransactionDTO.getTransactionType().isEscheat()) {
this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
CASH_ACCOUNTS_FOR_SAVINGS.SAVINGS_CONTROL.getValue(), CASH_ACCOUNTS_FOR_SAVINGS.ESCHEAT_LIABILITY.getValue(),
savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
}
/** /**
* Handle Interest Applications and reversals of Interest * Handle Interest Applications and reversals of Interest
* Applications * Applications
@@ -20,6 +20,7 @@ package org.apache.fineract.accounting.producttoaccountmapping.serialization;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_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.accountingRuleParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -207,6 +208,14 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue()) baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue())
.value(incomeFromPenaltyId).notNull().integerGreaterThanZero(); .value(incomeFromPenaltyId).notNull().integerGreaterThanZero();
final Boolean isDormancyTrackingActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element);
if(null != isDormancyTrackingActive && isDormancyTrackingActive){
final Long escheatLiabilityId = this.fromApiJsonHelper.extractLongNamed(
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.ESCHEAT_LIABILITY.getValue(), element);
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.ESCHEAT_LIABILITY.getValue())
.value(escheatLiabilityId).notNull().integerGreaterThanZero();
}
if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) { if (!accountType.equals(DepositAccountType.RECURRING_DEPOSIT) && !accountType.equals(DepositAccountType.FIXED_DEPOSIT)) {
final Long overdraftAccount = this.fromApiJsonHelper.extractLongNamed( final Long overdraftAccount = this.fromApiJsonHelper.extractLongNamed(
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element); SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
@@ -228,6 +228,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product
accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.LOSSES_WRITTEN_OFF.getValue(), gLAccountData); accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.LOSSES_WRITTEN_OFF.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_INTEREST)) { } else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.INCOME_FROM_INTEREST)) {
accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_INTEREST.getValue(), gLAccountData); accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.INCOME_FROM_INTEREST.getValue(), gLAccountData);
} else if (glAccountForSavings.equals(CASH_ACCOUNTS_FOR_SAVINGS.ESCHEAT_LIABILITY)) {
accountMappingDetails.put(SAVINGS_PRODUCT_ACCOUNTING_DATA_PARAMS.ESCHEAT_LIABILITY.getValue(), gLAccountData);
} }
} }
} }
@@ -19,6 +19,7 @@
package org.apache.fineract.accounting.producttoaccountmapping.service; package org.apache.fineract.accounting.producttoaccountmapping.service;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
@@ -225,6 +226,13 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), savingProductId, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), savingProductId,
CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.getValue()); CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.getValue());
final Boolean isDormancyTrackingActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element);
if(null != isDormancyTrackingActive && isDormancyTrackingActive){
this.savingsProductToGLAccountMappingHelper.saveSavingsToLiabilityAccountMapping(element,
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.ESCHEAT_LIABILITY.getValue(), savingProductId,
CASH_ACCOUNTS_FOR_SAVINGS.ESCHEAT_LIABILITY.getValue());
}
// advanced accounting mappings // advanced accounting mappings
this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null); this.savingsProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command, element, savingProductId, null);
this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null); this.savingsProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command, element, savingProductId, null);
@@ -222,6 +222,9 @@ public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMa
mergeSavingsToLiabilityAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(), mergeSavingsToLiabilityAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.TRANSFERS_SUSPENSE.getValue(),
savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.getValue(), savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.getValue(),
CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.toString(), changes); CASH_ACCOUNTS_FOR_SAVINGS.TRANSFERS_SUSPENSE.toString(), changes);
mergeSavingsToLiabilityAccountMappingChanges(element, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.ESCHEAT_LIABILITY.getValue(),
savingsProductId, CASH_ACCOUNTS_FOR_SAVINGS.ESCHEAT_LIABILITY.getValue(),
CASH_ACCOUNTS_FOR_SAVINGS.ESCHEAT_LIABILITY.toString(), changes);
break; break;
case ACCRUAL_PERIODIC: case ACCRUAL_PERIODIC:
break; break;
@@ -39,7 +39,8 @@ public enum JobName {
RECALCULATE_INTEREST_FOR_LOAN("Recalculate Interest For Loans"), // RECALCULATE_INTEREST_FOR_LOAN("Recalculate Interest For Loans"), //
GENERATE_RD_SCEHDULE("Generate Mandatory Savings Schedule"), // GENERATE_RD_SCEHDULE("Generate Mandatory Savings Schedule"), //
GENERATE_LOANLOSS_PROVISIONING("Generate Loan Loss Provisioning"), // GENERATE_LOANLOSS_PROVISIONING("Generate Loan Loss Provisioning"), //
POST_DIVIDENTS_FOR_SHARES("Post Dividends For Shares"); POST_DIVIDENTS_FOR_SHARES("Post Dividends For Shares"),
UPDATE_SAVINGS_DORMANT_ACCOUNTS("Update Savings Dormant Accounts");
private final String name; private final String name;
@@ -24,6 +24,8 @@ import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData; import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData; import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumData;
import org.joda.time.LocalDate;
/** /**
* Immutable data object for savings accounts. * Immutable data object for savings accounts.
@@ -43,13 +45,16 @@ public class SavingsAccountSummaryData {
//differentiate Individual, JLG or Group account //differentiate Individual, JLG or Group account
private final EnumOptionData accountType; private final EnumOptionData accountType;
private final SavingsAccountApplicationTimelineData timeline; private final SavingsAccountApplicationTimelineData timeline;
private final SavingsAccountSubStatusEnumData subStatus;
private final LocalDate lastActiveTransactionDate;
//differentiate deposit accounts Savings, FD and RD accounts //differentiate deposit accounts Savings, FD and RD accounts
private final EnumOptionData depositType; private final EnumOptionData depositType;
public SavingsAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId, public SavingsAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
final String productName, final String shortProductName, final SavingsAccountStatusEnumData status, final CurrencyData currency, final String productName, final String shortProductName, final SavingsAccountStatusEnumData status, final CurrencyData currency,
final BigDecimal accountBalance, final EnumOptionData accountType, final SavingsAccountApplicationTimelineData timeline, final EnumOptionData depositType) { final BigDecimal accountBalance, final EnumOptionData accountType, final SavingsAccountApplicationTimelineData timeline, final EnumOptionData depositType,
final SavingsAccountSubStatusEnumData subStatus, final LocalDate lastActiveTransactionDate) {
this.id = id; this.id = id;
this.accountNo = accountNo; this.accountNo = accountNo;
this.externalId = externalId; this.externalId = externalId;
@@ -62,5 +67,7 @@ public class SavingsAccountSummaryData {
this.accountType = accountType; this.accountType = accountType;
this.timeline = timeline; this.timeline = timeline;
this.depositType = depositType; this.depositType = depositType;
this.subStatus = subStatus;
this.lastActiveTransactionDate = lastActiveTransactionDate;
} }
} }
@@ -39,6 +39,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData; import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData; import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumData;
import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountApplicationTimelineData; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountApplicationTimelineData;
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountStatusEnumData; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountStatusEnumData;
@@ -258,6 +259,13 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
accountsSummary.append("avbu.username as activatedByUsername,"); accountsSummary.append("avbu.username as activatedByUsername,");
accountsSummary.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,"); accountsSummary.append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname,");
accountsSummary.append("sa.sub_status_enum as subStatusEnum, ");
accountsSummary.append("(select IFNULL(max(sat.transaction_date),sa.activatedon_date) ");
accountsSummary.append("from m_savings_account_transaction as sat ");
accountsSummary.append("where sat.is_reversed = 0 ");
accountsSummary.append("and sat.transaction_type_enum in (1,2) ");
accountsSummary.append("and sat.savings_account_id = sa.id) as lastActiveTransactionDate, ");
accountsSummary.append("sa.closedon_date as closedOnDate,"); accountsSummary.append("sa.closedon_date as closedOnDate,");
accountsSummary.append("cbu.username as closedByUsername,"); accountsSummary.append("cbu.username as closedByUsername,");
accountsSummary.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,"); accountsSummary.append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,");
@@ -340,6 +348,10 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
final String closedByUsername = rs.getString("closedByUsername"); final String closedByUsername = rs.getString("closedByUsername");
final String closedByFirstname = rs.getString("closedByFirstname"); final String closedByFirstname = rs.getString("closedByFirstname");
final String closedByLastname = rs.getString("closedByLastname"); final String closedByLastname = rs.getString("closedByLastname");
final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum");
final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum);
final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate");
final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate,
submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername,
@@ -349,7 +361,7 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
closedByLastname); closedByLastname);
return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency, accountBalance, return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency, accountBalance,
accountTypeData, timeline, depositTypeData); accountTypeData, timeline, depositTypeData, subStatus, lastActiveTransactionDate);
} }
} }
@@ -184,10 +184,10 @@ public class Charge extends AbstractPersistable<Long> {
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings"); .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings");
} }
if (!ChargeTimeType.fromInt(getChargeTimeType()).isWithdrawalFee() if (!(ChargeTimeType.fromInt(getChargeTimeType()).isWithdrawalFee() || ChargeTimeType.fromInt(getChargeTimeType()).isSavingsNoActivityFee())
&& ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) { && ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation) baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode("savings.charge.calculation.type.percentage.allowed.only.for.withdrawal"); .failWithCodeNoParameterAddedToErrorCode("savings.charge.calculation.type.percentage.allowed.only.for.withdrawal.or.NoActivity");
} }
} else if (isLoanCharge()) { } else if (isLoanCharge()) {
@@ -401,10 +401,10 @@ public class Charge extends AbstractPersistable<Long> {
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings"); .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings");
} }
if (!ChargeTimeType.fromInt(getChargeTimeType()).isWithdrawalFee() if (!(ChargeTimeType.fromInt(getChargeTimeType()).isWithdrawalFee() || ChargeTimeType.fromInt(getChargeTimeType()).isSavingsNoActivityFee())
&& ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) { && ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation) baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode("charge.calculation.type.percentage.allowed.only.for.withdrawal"); .failWithCodeNoParameterAddedToErrorCode("charge.calculation.type.percentage.allowed.only.for.withdrawal.or.noactivity");
} }
} else if (isClientCharge()) { } else if (isClientCharge()) {
if (!isAllowedClientChargeCalculationType()) { if (!isAllowedClientChargeCalculationType()) {
@@ -35,7 +35,9 @@ public enum ChargeTimeType {
// loan // loan
SHAREACCOUNT_ACTIVATION(13, "chargeTimeType.activation"), // only for loan SHAREACCOUNT_ACTIVATION(13, "chargeTimeType.activation"), // only for loan
SHARE_PURCHASE(14, "chargeTimeType.sharespurchase"), SHARE_PURCHASE(14, "chargeTimeType.sharespurchase"),
SHARE_REDEEM(15, "chargeTimeType.sharesredeem"); SHARE_REDEEM(15, "chargeTimeType.sharesredeem"),
SAVINGS_NOACTIVITY_FEE(16,"chargeTimeType.savingsNoActivityFee");
private final Integer value; private final Integer value;
private final String code; private final String code;
@@ -67,7 +69,8 @@ public enum ChargeTimeType {
public static Object[] validSavingsValues() { public static Object[] validSavingsValues() {
return new Integer[] { ChargeTimeType.SPECIFIED_DUE_DATE.getValue(), ChargeTimeType.SAVINGS_ACTIVATION.getValue(), return new Integer[] { ChargeTimeType.SPECIFIED_DUE_DATE.getValue(), ChargeTimeType.SAVINGS_ACTIVATION.getValue(),
ChargeTimeType.SAVINGS_CLOSURE.getValue(), ChargeTimeType.WITHDRAWAL_FEE.getValue(), ChargeTimeType.ANNUAL_FEE.getValue(), ChargeTimeType.SAVINGS_CLOSURE.getValue(), ChargeTimeType.WITHDRAWAL_FEE.getValue(), ChargeTimeType.ANNUAL_FEE.getValue(),
ChargeTimeType.MONTHLY_FEE.getValue(), ChargeTimeType.OVERDRAFT_FEE.getValue(), ChargeTimeType.WEEKLY_FEE.getValue() }; ChargeTimeType.MONTHLY_FEE.getValue(), ChargeTimeType.OVERDRAFT_FEE.getValue(), ChargeTimeType.WEEKLY_FEE.getValue(),
ChargeTimeType.SAVINGS_NOACTIVITY_FEE.getValue()};
} }
public static Object[] validClientValues() { public static Object[] validClientValues() {
@@ -127,6 +130,9 @@ public enum ChargeTimeType {
case 15: case 15:
chargeTimeType = SHARE_REDEEM; chargeTimeType = SHARE_REDEEM;
break; break;
case 16:
chargeTimeType = SAVINGS_NOACTIVITY_FEE;
break;
default: default:
chargeTimeType = INVALID; chargeTimeType = INVALID;
break; break;
@@ -155,6 +161,10 @@ public enum ChargeTimeType {
return this.value.equals(ChargeTimeType.WITHDRAWAL_FEE.getValue()); return this.value.equals(ChargeTimeType.WITHDRAWAL_FEE.getValue());
} }
public boolean isSavingsNoActivityFee() {
return this.value.equals(ChargeTimeType.SAVINGS_NOACTIVITY_FEE.getValue());
}
public boolean isAnnualFee() { public boolean isAnnualFee() {
return this.value.equals(ChargeTimeType.ANNUAL_FEE.getValue()); return this.value.equals(ChargeTimeType.ANNUAL_FEE.getValue());
} }
@@ -185,7 +195,7 @@ public enum ChargeTimeType {
public boolean isAllowedSavingsChargeTime() { public boolean isAllowedSavingsChargeTime() {
return isOnSpecifiedDueDate() || isSavingsActivation() || isSavingsClosure() || isWithdrawalFee() || isAnnualFee() return isOnSpecifiedDueDate() || isSavingsActivation() || isSavingsClosure() || isWithdrawalFee() || isAnnualFee()
|| isMonthlyFee() || isWeeklyFee() || isOverdraftFee(); || isMonthlyFee() || isWeeklyFee() || isOverdraftFee() || isSavingsNoActivityFee();
} }
public boolean isOverdraftFee() { public boolean isOverdraftFee() {
@@ -102,7 +102,7 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
// chargeTimeType(ChargeTimeType.SAVINGS_CLOSURE), // chargeTimeType(ChargeTimeType.SAVINGS_CLOSURE),
chargeTimeType(ChargeTimeType.WITHDRAWAL_FEE), chargeTimeType(ChargeTimeType.ANNUAL_FEE), chargeTimeType(ChargeTimeType.WITHDRAWAL_FEE), chargeTimeType(ChargeTimeType.ANNUAL_FEE),
chargeTimeType(ChargeTimeType.MONTHLY_FEE), chargeTimeType(ChargeTimeType.WEEKLY_FEE), chargeTimeType(ChargeTimeType.MONTHLY_FEE), chargeTimeType(ChargeTimeType.WEEKLY_FEE),
chargeTimeType(ChargeTimeType.OVERDRAFT_FEE)); chargeTimeType(ChargeTimeType.OVERDRAFT_FEE), chargeTimeType(ChargeTimeType.SAVINGS_NOACTIVITY_FEE));
} }
@Override @Override
@@ -91,6 +91,10 @@ public class ChargeEnumerations {
case SHARE_REDEEM: case SHARE_REDEEM:
optionData = new EnumOptionData(ChargeTimeType.SHARE_REDEEM.getValue().longValue(), ChargeTimeType.SHARE_REDEEM.getCode(), "Share Redeem") ; optionData = new EnumOptionData(ChargeTimeType.SHARE_REDEEM.getValue().longValue(), ChargeTimeType.SHARE_REDEEM.getCode(), "Share Redeem") ;
break ; break ;
case SAVINGS_NOACTIVITY_FEE:
optionData = new EnumOptionData(ChargeTimeType.SAVINGS_NOACTIVITY_FEE.getValue().longValue(), ChargeTimeType.SAVINGS_NOACTIVITY_FEE.getCode(),
"Saving No Activity Fee");
break;
default: default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid"); optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
break; break;
@@ -40,7 +40,8 @@ public enum SavingsAccountTransactionType {
WITHDRAW_TRANSFER(14, "savingsAccountTransactionType.withdrawTransfer"), // WITHDRAW_TRANSFER(14, "savingsAccountTransactionType.withdrawTransfer"), //
REJECT_TRANSFER(15, "savingsAccountTransactionType.rejectTransfer"), WRITTEN_OFF(16, "savingsAccountTransactionType.writtenoff"), // 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"); WITHHOLD_TAX(18, "savingsAccountTransactionType.withholdTax"),
ESCHEAT(19, "savingsAccountTransactionType.escheat");
private final Integer value; private final Integer value;
private final String code; private final String code;
@@ -109,6 +110,9 @@ public enum SavingsAccountTransactionType {
case 18: case 18:
savingsAccountTransactionType = SavingsAccountTransactionType.WITHHOLD_TAX; savingsAccountTransactionType = SavingsAccountTransactionType.WITHHOLD_TAX;
break; break;
case 19:
savingsAccountTransactionType = SavingsAccountTransactionType.ESCHEAT;
break;
} }
return savingsAccountTransactionType; return savingsAccountTransactionType;
} }
@@ -177,8 +181,12 @@ public enum SavingsAccountTransactionType {
return this.value.equals(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()); return this.value.equals(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue());
} }
public boolean isEscheat() {
return this.value.equals(SavingsAccountTransactionType.ESCHEAT.getValue());
}
public boolean isDebit() { public boolean isDebit() {
return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isPayCharge() || isIncomeFromInterest() || isWithHoldTax(); return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isPayCharge() || isIncomeFromInterest() || isWithHoldTax() || isEscheat();
} }
public boolean isCredit() { public boolean isCredit() {
@@ -161,6 +161,13 @@ public class SavingsApiConstants {
public static final String onHoldTransactionDateParamName = "transactionDate"; public static final String onHoldTransactionDateParamName = "transactionDate";
public static final String onHoldReversedParamName = "reversed"; public static final String onHoldReversedParamName = "reversed";
// Savings Dormancy
public static final String isDormancyTrackingActiveParamName = "isDormancyTrackingActive";
public static final String daysToInactiveParamName = "daysToInactive";
public static final String daysToDormancyParamName = "daysToDormancy";
public static final String daysToEscheatParamName = "daysToEscheat";
public static final Set<String> SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, public static final Set<String> SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName, monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName, digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName,
@@ -178,7 +185,10 @@ public class SavingsApiConstants {
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(),
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(), SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue(),
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(), allowOverdraftParamName, overdraftLimitParamName, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_INTEREST.getValue(),
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.ESCHEAT_LIABILITY.getValue(),
isDormancyTrackingActiveParamName, daysToDormancyParamName, daysToInactiveParamName, daysToEscheatParamName,
allowOverdraftParamName, overdraftLimitParamName,
nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, minRequiredBalanceParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, minRequiredBalanceParamName,
enforceMinRequiredBalanceParamName, minBalanceForInterestCalculationParamName, withHoldTaxParamName, taxGroupIdParamName)); enforceMinRequiredBalanceParamName, minBalanceForInterestCalculationParamName, withHoldTaxParamName, taxGroupIdParamName));
@@ -196,7 +206,8 @@ public class SavingsApiConstants {
"interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", "interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions",
"interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions",
nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, withHoldTaxParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, withHoldTaxParamName,
taxGroupIdParamName)); taxGroupIdParamName, isDormancyTrackingActiveParamName, daysToInactiveParamName, daysToDormancyParamName,
daysToInactiveParamName));
public static final Set<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, public static final Set<String> SAVINGS_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
dateFormatParamName, monthDayFormatParamName, staffIdParamName, accountNoParamName, externalIdParamName, clientIdParamName, dateFormatParamName, monthDayFormatParamName, staffIdParamName, accountNoParamName, externalIdParamName, clientIdParamName,
@@ -29,6 +29,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.staff.data.StaffData; import org.apache.fineract.organisation.staff.data.StaffData;
import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.apache.fineract.portfolio.tax.data.TaxGroupData;
import org.joda.time.LocalDate;
/** /**
* Immutable data object representing a savings account. * Immutable data object representing a savings account.
@@ -48,6 +49,7 @@ public class SavingsAccountData {
private final Long fieldOfficerId; private final Long fieldOfficerId;
private final String fieldOfficerName; private final String fieldOfficerName;
private final SavingsAccountStatusEnumData status; private final SavingsAccountStatusEnumData status;
private final SavingsAccountSubStatusEnumData subStatus;
private final SavingsAccountApplicationTimelineData timeline; private final SavingsAccountApplicationTimelineData timeline;
private final CurrencyData currency; private final CurrencyData currency;
private final BigDecimal nominalAnnualInterestRate; private final BigDecimal nominalAnnualInterestRate;
@@ -67,6 +69,11 @@ public class SavingsAccountData {
private final BigDecimal onHoldFunds; private final BigDecimal onHoldFunds;
private final boolean withHoldTax; private final boolean withHoldTax;
private final TaxGroupData taxGroup; private final TaxGroupData taxGroup;
private final LocalDate lastActiveTransactionDate;
private final boolean isDormancyTrackingActive;
private final Integer daysToInactive;
private final Integer daysToDormancy;
private final Integer daysToEscheat;
// associations // associations
private final SavingsAccountSummaryData summary; private final SavingsAccountSummaryData summary;
@@ -96,16 +103,18 @@ public class SavingsAccountData {
public static SavingsAccountData instance(final Long id, final String accountNo, final EnumOptionData depositType, 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, final String externalId, final Long groupId, final String groupName, final Long clientId, final String clientName,
final Long productId, final String productName, final Long fieldOfficerId, final String fieldOfficerName, final Long productId, final String productName, final Long fieldOfficerId, final String fieldOfficerName,
final SavingsAccountStatusEnumData status, final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency, final SavingsAccountStatusEnumData status, SavingsAccountSubStatusEnumData subStatus, final SavingsAccountApplicationTimelineData timeline,
final BigDecimal interestRate, final EnumOptionData interestCompoundingPeriodType, final CurrencyData currency, final BigDecimal interestRate,
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType,
final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType,
final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType,
final SavingsAccountSummaryData summary, final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean withdrawalFeeForTransfers, final SavingsAccountSummaryData summary, final boolean allowOverdraft,
final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance,
final BigDecimal minBalanceForInterestCalculation, final BigDecimal onHoldFunds, final boolean enforceMinRequiredBalance, final BigDecimal minBalanceForInterestCalculation,
final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, final BigDecimal onHoldFunds, final BigDecimal nominalAnnualInterestRateOverdraft,
final boolean withHoldTax, final TaxGroupData taxGroup) { final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup,
final LocalDate lastActiveTransactionDate, final boolean isDormancyTrackingActive, final Integer daysToInactive,
final Integer daysToDormancy, final Integer daysToEscheat) {
final Collection<SavingsProductData> productOptions = null; final Collection<SavingsProductData> productOptions = null;
final Collection<StaffData> fieldOfficerOptions = null; final Collection<StaffData> fieldOfficerOptions = null;
@@ -121,14 +130,15 @@ public class SavingsAccountData {
final Collection<ChargeData> chargeOptions = null; final Collection<ChargeData> chargeOptions = null;
return new SavingsAccountData(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, return new SavingsAccountData(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, interestRate, interestCompoundingPeriodType, productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, interestRate,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions,
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
minOverdraftForInterestCalculation, withHoldTax, taxGroup); nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup,
lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
public static SavingsAccountData lookup(final Long accountId, final String accountNo, final EnumOptionData depositType) { public static SavingsAccountData lookup(final Long accountId, final String accountNo, final EnumOptionData depositType) {
@@ -184,16 +194,23 @@ public class SavingsAccountData {
final Collection<ChargeData> chargeOptions = null; final Collection<ChargeData> chargeOptions = null;
final boolean withHoldTax = false; final boolean withHoldTax = false;
final TaxGroupData taxGroup = null; final TaxGroupData taxGroup = null;
final SavingsAccountSubStatusEnumData subStatus = null;
final LocalDate lastActiveTransactionDate = null;
final boolean isDormancyTrackingActive = false;
final Integer daysToInactive = null;
final Integer daysToDormancy = null;
final Integer daysToEscheat = null;
return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, interestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions,
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
minOverdraftForInterestCalculation, withHoldTax, taxGroup); nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup,
lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
public static SavingsAccountData lookupWithProductDetails(final Long accountId, final String accountNo, public static SavingsAccountData lookupWithProductDetails(final Long accountId, final String accountNo,
@@ -247,16 +264,23 @@ public class SavingsAccountData {
final Collection<ChargeData> chargeOptions = null; final Collection<ChargeData> chargeOptions = null;
final boolean withHoldTax = false; final boolean withHoldTax = false;
final TaxGroupData taxGroup = null; final TaxGroupData taxGroup = null;
final SavingsAccountSubStatusEnumData subStatus = null;
final LocalDate lastActiveTransactionDate = null;
final boolean isDormancyTrackingActive = false;
final Integer daysToInactive = null;
final Integer daysToDormancy = null;
final Integer daysToEscheat = null;
return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, interestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions,
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
minOverdraftForInterestCalculation, withHoldTax, taxGroup); nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup,
lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template, public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template,
@@ -280,17 +304,19 @@ public class SavingsAccountData {
return new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId, account.groupId, return new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId, account.groupId,
account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName,
account.fieldOfficerId, account.fieldOfficerName, account.status, account.timeline, account.currency, account.fieldOfficerId, account.fieldOfficerName, account.status, account.subStatus, account.timeline,
account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType,
account.interestCalculationType, account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.interestPostingPeriodType, account.interestCalculationType, account.interestCalculationDaysInYearType,
account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.summary, account.minRequiredOpeningBalance, account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers,
transactions, template.productOptions, template.fieldOfficerOptions, template.interestCompoundingPeriodTypeOptions, account.summary, transactions, template.productOptions, template.fieldOfficerOptions,
template.interestPostingPeriodTypeOptions, template.interestCalculationTypeOptions, template.interestCompoundingPeriodTypeOptions, template.interestPostingPeriodTypeOptions,
template.interestCalculationDaysInYearTypeOptions, template.lockinPeriodFrequencyTypeOptions, template.interestCalculationTypeOptions, template.interestCalculationDaysInYearTypeOptions,
template.withdrawalFeeTypeOptions, charges, template.chargeOptions, account.allowOverdraft, account.overdraftLimit, template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions, charges, template.chargeOptions, account.allowOverdraft,
account.minRequiredBalance, account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation, account.overdraftLimit, account.minRequiredBalance, account.enforceMinRequiredBalance,
account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation, account.minBalanceForInterestCalculation, account.onHoldFunds, account.nominalAnnualInterestRateOverdraft,
account.withHoldTax, account.taxGroup); account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup,
account.lastActiveTransactionDate, account.isDormancyTrackingActive, account.daysToInactive,
account.daysToDormancy, account.daysToEscheat);
} }
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, public static SavingsAccountData withTemplateOptions(final SavingsAccountData account,
@@ -305,16 +331,17 @@ public class SavingsAccountData {
return new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId, account.groupId, return new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId, account.groupId,
account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName,
account.fieldOfficerId, account.fieldOfficerName, account.status, account.timeline, account.currency, account.fieldOfficerId, account.fieldOfficerName, account.status, account.subStatus, account.timeline,
account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType,
account.interestCalculationType, account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, account.interestPostingPeriodType, account.interestCalculationType, account.interestCalculationDaysInYearType,
account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.summary, account.minRequiredOpeningBalance, account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers,
transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, account.summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions, account.allowOverdraft, account.overdraftLimit, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, account.allowOverdraft,
account.minRequiredBalance, account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation, account.overdraftLimit, account.minRequiredBalance, account.enforceMinRequiredBalance,
account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation, account.minBalanceForInterestCalculation, account.onHoldFunds, account.nominalAnnualInterestRateOverdraft,
account.withHoldTax, account.taxGroup); account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup, account.lastActiveTransactionDate,
account.isDormancyTrackingActive, account.daysToInactive, account.daysToDormancy, account.daysToEscheat);
} }
public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId,
@@ -370,38 +397,47 @@ public class SavingsAccountData {
final Collection<SavingsAccountChargeData> charges = null; final Collection<SavingsAccountChargeData> charges = null;
final Collection<ChargeData> chargeOptions = null; final Collection<ChargeData> chargeOptions = null;
final SavingsAccountSubStatusEnumData subStatus = null;
final LocalDate lastActiveTransactionDate = null;
final boolean isDormancyTrackingActive = false;
final Integer daysToInactive = null;
final Integer daysToDormancy = null;
final Integer daysToEscheat = null;
return new SavingsAccountData(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, return new SavingsAccountData(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, interestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions,
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
minOverdraftForInterestCalculation, withHoldTax, taxGroup); nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup,
lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
private SavingsAccountData(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId, private SavingsAccountData(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId,
final Long groupId, final String groupName, final Long clientId, final String clientName, final Long productId, final Long groupId, final String groupName, final Long clientId, final String clientName, final Long productId,
final String productName, final Long fieldofficerId, final String fieldofficerName, final SavingsAccountStatusEnumData status, final String productName, final Long fieldofficerId, final String fieldofficerName, final SavingsAccountStatusEnumData status,
final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final SavingsAccountSubStatusEnumData subStatus, final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency,
final EnumOptionData interestPeriodType, final EnumOptionData interestPostingPeriodType, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestPeriodType,
final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType,
final SavingsAccountSummaryData summary, final Collection<SavingsAccountTransactionData> transactions, final boolean withdrawalFeeForTransfers, final SavingsAccountSummaryData summary,
final Collection<SavingsProductData> productOptions, final Collection<StaffData> fieldOfficerOptions, final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsProductData> productOptions,
final Collection<StaffData> fieldOfficerOptions,
final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions, final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions,
final Collection<EnumOptionData> interestPostingPeriodTypeOptions, final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOptions, final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions, final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions, final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions, final Collection<SavingsAccountChargeData> charges, final Collection<ChargeData> chargeOptions,
final Collection<SavingsAccountChargeData> charges, final Collection<ChargeData> chargeOptions, final boolean allowOverdraft, final boolean allowOverdraft, final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance,
final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal minBalanceForInterestCalculation,
final BigDecimal minBalanceForInterestCalculation, final BigDecimal onHoldFunds, final BigDecimal onHoldFunds, final BigDecimal nominalAnnualInterestRateOverdraft,
final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup,
final boolean withHoldTax, final TaxGroupData taxGroup) { final LocalDate lastActiveTransactionDate, final boolean isDormancyTrackingActive, final Integer daysToInactive,
final Integer daysToDormancy, final Integer daysToEscheat) {
this.id = id; this.id = id;
this.accountNo = accountNo; this.accountNo = accountNo;
this.depositType = depositType; this.depositType = depositType;
@@ -415,6 +451,7 @@ public class SavingsAccountData {
this.fieldOfficerId = fieldofficerId; this.fieldOfficerId = fieldofficerId;
this.fieldOfficerName = fieldofficerName; this.fieldOfficerName = fieldofficerName;
this.status = status; this.status = status;
this.subStatus = subStatus;
this.timeline = timeline; this.timeline = timeline;
this.currency = currency; this.currency = currency;
this.nominalAnnualInterestRate = nominalAnnualInterestRate; this.nominalAnnualInterestRate = nominalAnnualInterestRate;
@@ -461,6 +498,11 @@ public class SavingsAccountData {
this.onHoldFunds = onHoldFunds; this.onHoldFunds = onHoldFunds;
this.withHoldTax = withHoldTax; this.withHoldTax = withHoldTax;
this.taxGroup = taxGroup; this.taxGroup = taxGroup;
this.lastActiveTransactionDate = lastActiveTransactionDate;
this.isDormancyTrackingActive = isDormancyTrackingActive;
this.daysToInactive = daysToInactive;
this.daysToDormancy = daysToDormancy;
this.daysToEscheat = daysToEscheat;
} }
private SavingsAccountChargeData getWithdrawalFee() { private SavingsAccountChargeData getWithdrawalFee() {
@@ -0,0 +1,54 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.savings.data;
/**
* Immutable data object represent savings account sub-status enumerations.
*/
public class SavingsAccountSubStatusEnumData {
private final Long id;
@SuppressWarnings("unused")
private final String code;
@SuppressWarnings("unused")
private final String value;
@SuppressWarnings("unused")
private final boolean none;
@SuppressWarnings("unused")
private final boolean inactive;
@SuppressWarnings("unused")
private final boolean dormant;
@SuppressWarnings("unused")
private final boolean escheat;
public SavingsAccountSubStatusEnumData(final Long id, final String code, final String value, final boolean none,
final boolean inactive, final boolean dormant, final boolean escheat) {
this.id = id;
this.code = code;
this.value = value;
this.none = none;
this.inactive = inactive;
this.dormant = dormant;
this.escheat = escheat;
}
public Long id() {
return this.id;
}
}
@@ -43,6 +43,7 @@ public class SavingsAccountTransactionEnumData {
private final boolean writtenoff; private final boolean writtenoff;
private final boolean overdraftFee = true; private final boolean overdraftFee = true;
private final boolean withholdTax; private final boolean withholdTax;
private final boolean escheat;
public SavingsAccountTransactionEnumData(final Long id, final String code, final String value) { public SavingsAccountTransactionEnumData(final Long id, final String code, final String value) {
this.id = id; this.id = id;
@@ -62,6 +63,7 @@ public class SavingsAccountTransactionEnumData {
this.writtenoff = Long.valueOf(SavingsAccountTransactionType.WRITTEN_OFF.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.overdraftInterest = Long.valueOf(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()).equals(this.id);
this.withholdTax = Long.valueOf(SavingsAccountTransactionType.WITHHOLD_TAX.getValue()).equals(this.id); this.withholdTax = Long.valueOf(SavingsAccountTransactionType.WITHHOLD_TAX.getValue()).equals(this.id);
this.escheat = Long.valueOf(SavingsAccountTransactionType.ESCHEAT.getValue()).equals(this.id);
// this.overdraftFee = // this.overdraftFee =
// Long.valueOf(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()).equals(this.id); // Long.valueOf(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()).equals(this.id);
} }
@@ -134,4 +136,8 @@ public class SavingsAccountTransactionEnumData {
return this.dividendPayout; return this.dividendPayout;
} }
public boolean isEscheat() {
return this.escheat;
}
} }
@@ -86,6 +86,10 @@ public class SavingsProductData {
private final Collection<ChargeData> chargeOptions; private final Collection<ChargeData> chargeOptions;
private final Collection<ChargeData> penaltyOptions; private final Collection<ChargeData> penaltyOptions;
private final Collection<TaxGroupData> taxGroupOptions; private final Collection<TaxGroupData> taxGroupOptions;
private final Boolean isDormancyTrackingActive;
private final Long daysToInactive;
private final Long daysToDormancy;
private final Long daysToEscheat;
public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType, public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
@@ -122,6 +126,10 @@ public class SavingsProductData {
final BigDecimal minOverdraftForInterestCalculation = null; final BigDecimal minOverdraftForInterestCalculation = null;
final boolean withHoldTax = false; final boolean withHoldTax = false;
final TaxGroupData taxGroup = null; final TaxGroupData taxGroup = null;
final Boolean isDormancyTrackingActive = false;
final Long daysToInactive = null;
final Long daysToDormancy = null;
final Long daysToEscheat = null;
return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
@@ -132,7 +140,7 @@ public class SavingsProductData {
accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax,
taxGroup, taxGroupOptions); taxGroup, taxGroupOptions, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
public static SavingsProductData withCharges(final SavingsProductData product, final Collection<ChargeData> charges) { public static SavingsProductData withCharges(final SavingsProductData product, final Collection<ChargeData> charges) {
@@ -148,7 +156,8 @@ public class SavingsProductData {
product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings, product.allowOverdraft, product.overdraftLimit, product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings, product.allowOverdraft, product.overdraftLimit,
product.minRequiredBalance, product.enforceMinRequiredBalance, product.minBalanceForInterestCalculation, product.minRequiredBalance, product.enforceMinRequiredBalance, product.minBalanceForInterestCalculation,
product.nominalAnnualInterestRateOverdraft, product.minOverdraftForInterestCalculation, product.withHoldTax, product.nominalAnnualInterestRateOverdraft, product.minOverdraftForInterestCalculation, product.withHoldTax,
product.taxGroup, product.taxGroupOptions); product.taxGroup, product.taxGroupOptions, product.isDormancyTrackingActive, product.daysToInactive,
product.daysToDormancy, product.daysToEscheat);
} }
/** /**
@@ -181,7 +190,8 @@ public class SavingsProductData {
existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, existingProduct.allowOverdraft, existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, existingProduct.allowOverdraft,
existingProduct.overdraftLimit, existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance, existingProduct.overdraftLimit, existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance,
existingProduct.minBalanceForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft, existingProduct.minBalanceForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft,
existingProduct.minOverdraftForInterestCalculation, existingProduct.withHoldTax, existingProduct.taxGroup, taxGroupOptions); existingProduct.minOverdraftForInterestCalculation, existingProduct.withHoldTax, existingProduct.taxGroup, taxGroupOptions,
existingProduct.isDormancyTrackingActive, existingProduct.daysToInactive, existingProduct.daysToDormancy, existingProduct.daysToEscheat);
} }
public static SavingsProductData withAccountingDetails(final SavingsProductData existingProduct, public static SavingsProductData withAccountingDetails(final SavingsProductData existingProduct,
@@ -217,7 +227,8 @@ public class SavingsProductData {
existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance, existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance,
existingProduct.minBalanceForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft, existingProduct.minBalanceForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft,
existingProduct.minOverdraftForInterestCalculation, existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.minOverdraftForInterestCalculation, existingProduct.withHoldTax, existingProduct.taxGroup,
existingProduct.taxGroupOptions); existingProduct.taxGroupOptions, existingProduct.isDormancyTrackingActive, existingProduct.daysToInactive,
existingProduct.daysToDormancy, existingProduct.daysToEscheat);
} }
public static SavingsProductData instance(final Long id, final String name, final String shortName, final String description, public static SavingsProductData instance(final Long id, final String name, final String shortName, final String description,
@@ -228,7 +239,8 @@ public class SavingsProductData {
final EnumOptionData accountingType, final boolean allowOverdraft, final BigDecimal overdraftLimit, final EnumOptionData accountingType, final boolean allowOverdraft, final BigDecimal overdraftLimit,
final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance,
final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft,
final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup) { final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup,
final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat) {
final Map<String, Object> accountingMappings = null; final Map<String, Object> accountingMappings = null;
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null; final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
@@ -259,7 +271,7 @@ public class SavingsProductData {
accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax,
taxGroup, taxGroupOptions); taxGroup, taxGroupOptions, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
public static SavingsProductData lookup(final Long id, final String name) { public static SavingsProductData lookup(final Long id, final String name) {
@@ -305,6 +317,10 @@ public class SavingsProductData {
final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null; final Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings = null;
final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null; final Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = null;
final Collection<TaxGroupData> taxGroupOptions = null; final Collection<TaxGroupData> taxGroupOptions = null;
final Boolean isDormancyTrackingActive = null;
final Long daysToInactive = null;
final Long daysToDormancy = null;
final Long daysToEscheat = null;
return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, return new SavingsProductData(id, name, shortName, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
@@ -315,7 +331,7 @@ public class SavingsProductData {
accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings,
penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax,
taxGroup, taxGroupOptions); taxGroup, taxGroupOptions, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
private SavingsProductData(final Long id, final String name, final String shortName, final String description, private SavingsProductData(final Long id, final String name, final String shortName, final String description,
@@ -338,7 +354,8 @@ public class SavingsProductData {
final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance,
final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft,
final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup, final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup,
final Collection<TaxGroupData> taxGroupOptions) { final Collection<TaxGroupData> taxGroupOptions, final Boolean isDormancyTrackingActive, final Long daysToInactive,
final Long daysToDormancy, final Long daysToEscheat) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.shortName = shortName; this.shortName = shortName;
@@ -391,6 +408,10 @@ public class SavingsProductData {
this.taxGroup = taxGroup; this.taxGroup = taxGroup;
this.withHoldTax = withHoldTax; this.withHoldTax = withHoldTax;
this.taxGroupOptions = taxGroupOptions; this.taxGroupOptions = taxGroupOptions;
this.isDormancyTrackingActive = isDormancyTrackingActive;
this.daysToInactive = daysToInactive;
this.daysToDormancy = daysToDormancy;
this.daysToEscheat = daysToEscheat;
} }
public boolean hasAccountingEnabled() { public boolean hasAccountingEnabled() {
@@ -44,6 +44,10 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNam
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; 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.withHoldTaxParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToInactiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToDormancyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToEscheatParamName;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -64,6 +68,7 @@ import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodTyp
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType; import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
import org.joda.time.MonthDay; import org.joda.time.MonthDay;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -233,6 +238,27 @@ public class SavingsProductDataValidator {
} }
} }
//dormancy
final Boolean isDormancyActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element);
if(null != isDormancyActive && isDormancyActive){
final Long daysToInact = this.fromApiJsonHelper.extractLongNamed(daysToInactiveParamName, element);
baseDataValidator.reset().parameter(daysToInactiveParamName).value(daysToInact).notNull().longGreaterThanZero();
final Long daysToDor = this.fromApiJsonHelper.extractLongNamed(daysToDormancyParamName, element);
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).notNull().longGreaterThanZero();
final Long daysToEsc = this.fromApiJsonHelper.extractLongNamed(daysToEscheatParamName, element);
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).notNull().longGreaterThanZero();
if(null != daysToInact
&& null != daysToDor
&& null != daysToEsc){
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).longGreaterThanNumber(daysToInact);
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).longGreaterThanNumber(daysToDor);
}
}
// accounting related data validation // accounting related data validation
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault()); final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3); baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3);
@@ -284,6 +310,13 @@ public class SavingsProductDataValidator {
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writtenoff) baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writtenoff)
.notNull().integerGreaterThanZero(); .notNull().integerGreaterThanZero();
if(null != isDormancyActive && isDormancyActive){
final Long escheatLiabilityAccountId = this.fromApiJsonHelper.extractLongNamed(
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.ESCHEAT_LIABILITY.getValue(), element);
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.ESCHEAT_LIABILITY.getValue())
.value(escheatLiabilityAccountId).notNull().integerGreaterThanZero();
}
validatePaymentChannelFundSourceMappings(baseDataValidator, element); validatePaymentChannelFundSourceMappings(baseDataValidator, element);
validateChargeToIncomeAccountMappings(baseDataValidator, element); validateChargeToIncomeAccountMappings(baseDataValidator, element);
} }
@@ -302,7 +335,7 @@ public class SavingsProductDataValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
public void validateForUpdate(final String json) { public void validateForUpdate(final String json, final SavingsProduct product) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
@@ -453,6 +486,42 @@ public class SavingsProductDataValidator {
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writtenoff) baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.LOSSES_WRITTEN_OFF.getValue()).value(writtenoff)
.ignoreIfNull().integerGreaterThanZero(); .ignoreIfNull().integerGreaterThanZero();
//dormancy
final Boolean isDormancyActive = this.fromApiJsonHelper.parameterExists(isDormancyTrackingActiveParamName, element)?
this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element):
product.isDormancyTrackingActive();
if(null != isDormancyActive && isDormancyActive){
final Long daysToInact = this.fromApiJsonHelper.parameterExists(daysToInactiveParamName, element)?
this.fromApiJsonHelper.extractLongNamed(daysToInactiveParamName, element):
product.getDaysToInactive();
baseDataValidator.reset().parameter(daysToInactiveParamName).value(daysToInact).notNull().longGreaterThanZero();
final Long daysToDor = this.fromApiJsonHelper.parameterExists(daysToDormancyParamName, element)?
this.fromApiJsonHelper.extractLongNamed(daysToDormancyParamName, element):
product.getDaysToDormancy();
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).notNull().longGreaterThanZero();
final Long daysToEsc = this.fromApiJsonHelper.parameterExists(daysToEscheatParamName, element)?
this.fromApiJsonHelper.extractLongNamed(daysToEscheatParamName, element):
product.getDaysToEscheat();
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).notNull().longGreaterThanZero();
if(null != daysToInact
&& null != daysToDor
&& null != daysToEsc){
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).longGreaterThanNumber(daysToInact);
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).longGreaterThanNumber(daysToDor);
}
if(this.fromApiJsonHelper.parameterExists(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.ESCHEAT_LIABILITY.getValue(), element)){
final Long escheatLiabilityAccountId = this.fromApiJsonHelper.extractLongNamed(
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.ESCHEAT_LIABILITY.getValue(), element);
baseDataValidator.reset().parameter(SAVINGS_PRODUCT_ACCOUNTING_PARAMS.ESCHEAT_LIABILITY.getValue())
.value(escheatLiabilityAccountId).notNull().integerGreaterThanZero();
}
}
validatePaymentChannelFundSourceMappings(baseDataValidator, element); validatePaymentChannelFundSourceMappings(baseDataValidator, element);
validateChargeToIncomeAccountMappings(baseDataValidator, element); validateChargeToIncomeAccountMappings(baseDataValidator, element);
validateOverdraftParams(baseDataValidator, element); validateOverdraftParams(baseDataValidator, element);
@@ -114,6 +114,7 @@ import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatter;
import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.data.jpa.domain.AbstractPersistable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
@@ -154,6 +155,9 @@ public class SavingsAccount extends AbstractPersistable<Long> {
@Column(name = "status_enum", nullable = false) @Column(name = "status_enum", nullable = false)
protected Integer status; protected Integer status;
@Column(name = "sub_status_enum", nullable = false)
protected Integer sub_status = 0;
@Column(name = "account_type_enum", nullable = false) @Column(name = "account_type_enum", nullable = false)
protected Integer accountType; protected Integer accountType;
@@ -902,6 +906,11 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
if(this.sub_status.equals(SavingsAccountSubStatusEnum.INACTIVE.getValue())
|| this.sub_status.equals(SavingsAccountSubStatusEnum.DORMANT.getValue())){
this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue();
}
return transaction; return transaction;
} }
@@ -987,6 +996,10 @@ public class SavingsAccount extends AbstractPersistable<Long> {
// auto pay withdrawal fee // auto pay withdrawal fee
payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), transactionDTO.getAppUser()); payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), transactionDTO.getAppUser());
} }
if(this.sub_status.equals(SavingsAccountSubStatusEnum.INACTIVE.getValue())
|| this.sub_status.equals(SavingsAccountSubStatusEnum.DORMANT.getValue())){
this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue();
}
return transaction; return transaction;
} }
@@ -2744,4 +2757,37 @@ public class SavingsAccount extends AbstractPersistable<Long> {
return recalucateDailyBalance; return recalucateDailyBalance;
} }
public void setSubStatusInactive(AppUser appUser) {
this.sub_status = SavingsAccountSubStatusEnum.INACTIVE.getValue();
LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
for (SavingsAccountCharge charge : this.charges()) {
if (charge.isSavingsNoActivity() && charge.isActive()) {
charge.updateWithdralFeeAmount(this.getAccountBalance());
this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, appUser);
}
}
recalculateDailyBalances(Money.zero(this.currency), transactionDate);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
}
public void setSubStatusDormant() {
this.sub_status = SavingsAccountSubStatusEnum.DORMANT.getValue();
}
public void escheat(AppUser appUser) {
this.status = SavingsAccountStatusType.CLOSED.getValue();
this.sub_status = SavingsAccountSubStatusEnum.ESCHEAT.getValue();
this.closedOnDate = DateUtils.getDateOfTenant();
this.closedBy = appUser;
LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
if(this.getSummary().getAccountBalance(this.getCurrency()).isGreaterThanZero()){
SavingsAccountTransaction transaction = SavingsAccountTransaction.escheat(this, transactionDate, appUser);
this.transactions.add(transaction);
}
recalculateDailyBalances(Money.zero(this.currency), transactionDate);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
}
} }
@@ -657,6 +657,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
return ChargeTimeType.fromInt(this.chargeTime).isSavingsActivation(); return ChargeTimeType.fromInt(this.chargeTime).isSavingsActivation();
} }
public boolean isSavingsNoActivity(){
return ChargeTimeType.fromInt(this.chargeTime).isSavingsNoActivityFee();
}
public boolean isSavingsClosure() { public boolean isSavingsClosure() {
return ChargeTimeType.fromInt(this.chargeTime).isSavingsClosure(); return ChargeTimeType.fromInt(this.chargeTime).isSavingsClosure();
} }
@@ -0,0 +1,85 @@
/**
* 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;
/**
* Enum representation of {@link SavingsAccount} sub-status states.
*/
public enum SavingsAccountSubStatusEnum {
NONE(0, "SavingsAccountSubStatusEnum.none"), //
INACTIVE(100, "SavingsAccountSubStatusEnum.inactive"), //
DORMANT(200, "SavingsAccountSubStatusEnum.dormant"),
ESCHEAT(300,"SavingsAccountSubStatusEnum.escheat");
private final Integer value;
private final String code;
public static SavingsAccountSubStatusEnum fromInt(final Integer type) {
SavingsAccountSubStatusEnum enumeration = SavingsAccountSubStatusEnum.NONE;
if(null != type){
switch (type) {
case 100:
enumeration = SavingsAccountSubStatusEnum.INACTIVE;
break;
case 200:
enumeration = SavingsAccountSubStatusEnum.DORMANT;
break;
case 300:
enumeration = SavingsAccountSubStatusEnum.ESCHEAT;
break;
}
}
return enumeration;
}
private SavingsAccountSubStatusEnum(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public boolean hasStateOf(final SavingsAccountSubStatusEnum state) {
return this.value.equals(state.getValue());
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isSubStatusInactive() {
return this.value.equals(SavingsAccountSubStatusEnum.INACTIVE.getValue());
}
public boolean isSubStatusDormant() {
return this.value.equals(SavingsAccountSubStatusEnum.DORMANT.getValue());
}
public boolean isSubStatusNone() {
return this.value.equals(SavingsAccountSubStatusEnum.NONE.getValue());
}
public boolean isSubStatusEscheat() {
return this.value.equals(SavingsAccountSubStatusEnum.ESCHEAT.getValue());
}
}
@@ -228,6 +228,15 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
return accountTransaction; return accountTransaction;
} }
public static SavingsAccountTransaction escheat(final SavingsAccount savingsAccount, final LocalDate date,
final AppUser appUser) {
final boolean isReversed = false;
final PaymentDetail paymentDetail = null;
return new SavingsAccountTransaction(savingsAccount, savingsAccount.office(), paymentDetail,
SavingsAccountTransactionType.ESCHEAT.getValue(), date, new Date(), savingsAccount.getSummary()
.getAccountBalance(), isReversed, appUser);
}
public static void updateTaxDetails(final Map<TaxComponent, BigDecimal> taxDetails, final SavingsAccountTransaction accountTransaction) { public static void updateTaxDetails(final Map<TaxComponent, BigDecimal> taxDetails, final SavingsAccountTransaction accountTransaction) {
if (taxDetails != null) { if (taxDetails != null) {
for (Map.Entry<TaxComponent, BigDecimal> mapEntry : taxDetails.entrySet()) { for (Map.Entry<TaxComponent, BigDecimal> mapEntry : taxDetails.entrySet()) {
@@ -46,6 +46,10 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNam
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; 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.taxGroupIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToInactiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToDormancyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToEscheatParamName;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
@@ -191,6 +195,18 @@ public class SavingsProduct extends AbstractPersistable<Long> {
@JoinColumn(name = "tax_group_id") @JoinColumn(name = "tax_group_id")
private TaxGroup taxGroup; private TaxGroup taxGroup;
@Column(name = "is_dormancy_tracking_active")
private Boolean isDormancyTrackingActive;
@Column(name = "days_to_inactive")
private Long daysToInactive;
@Column(name = "days_to_dormancy")
private Long daysToDormancy;
@Column(name = "days_to_escheat")
private Long daysToEscheat;
public static SavingsProduct createNew(final String name, final String shortName, final String description, public static SavingsProduct createNew(final String name, final String shortName, final String description,
final MonetaryCurrency currency, final BigDecimal interestRate, final MonetaryCurrency currency, final BigDecimal interestRate,
final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
@@ -201,13 +217,15 @@ public class SavingsProduct extends AbstractPersistable<Long> {
final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance, final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance,
final BigDecimal minRequiredBalance, final BigDecimal minBalanceForInterestCalculation, final BigDecimal minRequiredBalance, final BigDecimal minBalanceForInterestCalculation,
final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation,
boolean withHoldTax, TaxGroup taxGroup) { boolean withHoldTax, TaxGroup taxGroup,
final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat) {
return new SavingsProduct(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, return new SavingsProduct(name, shortName, description, currency, interestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges,
allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, minBalanceForInterestCalculation, allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, minBalanceForInterestCalculation,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup); nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup,
isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
protected SavingsProduct() { protected SavingsProduct() {
@@ -226,7 +244,7 @@ public class SavingsProduct extends AbstractPersistable<Long> {
this(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, this(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, allowOverdraft, overdraftLimit, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, allowOverdraft, overdraftLimit,
false, null, minBalanceForInterestCalculation, null, null, withHoldTax, taxGroup); false, null, minBalanceForInterestCalculation, null, null, withHoldTax, taxGroup, null, null, null, null);
} }
protected SavingsProduct(final String name, final String shortName, final String description, final MonetaryCurrency currency, protected SavingsProduct(final String name, final String shortName, final String description, final MonetaryCurrency currency,
@@ -238,7 +256,8 @@ public class SavingsProduct extends AbstractPersistable<Long> {
final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance, final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance,
final BigDecimal minRequiredBalance, BigDecimal minBalanceForInterestCalculation, final BigDecimal minRequiredBalance, BigDecimal minBalanceForInterestCalculation,
final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation,
final boolean withHoldTax, final TaxGroup taxGroup) { final boolean withHoldTax, final TaxGroup taxGroup,
final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat) {
this.name = name; this.name = name;
this.shortName = shortName; this.shortName = shortName;
@@ -283,6 +302,11 @@ public class SavingsProduct extends AbstractPersistable<Long> {
this.minBalanceForInterestCalculation = minBalanceForInterestCalculation; this.minBalanceForInterestCalculation = minBalanceForInterestCalculation;
this.withHoldTax = withHoldTax; this.withHoldTax = withHoldTax;
this.taxGroup = taxGroup; this.taxGroup = taxGroup;
this.isDormancyTrackingActive = isDormancyTrackingActive;
this.daysToInactive = daysToInactive;
this.daysToDormancy = daysToDormancy;
this.daysToEscheat = daysToEscheat;
} }
/** /**
@@ -542,6 +566,36 @@ public class SavingsProduct extends AbstractPersistable<Long> {
this.taxGroup = null; this.taxGroup = null;
} }
if(command.isChangeInBooleanParameterNamed(isDormancyTrackingActiveParamName, this.isDormancyTrackingActive)){
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isDormancyTrackingActiveParamName);
actualChanges.put(isDormancyTrackingActiveParamName, newValue);
this.isDormancyTrackingActive = newValue;
}
if(command.isChangeInLongParameterNamed(daysToInactiveParamName, this.daysToInactive)){
final Long newValue = command.longValueOfParameterNamed(daysToInactiveParamName);
actualChanges.put(daysToInactiveParamName, newValue);
this.daysToInactive = newValue;
}
if(command.isChangeInLongParameterNamed(daysToDormancyParamName, this.daysToDormancy)){
final Long newValue = command.longValueOfParameterNamed(daysToDormancyParamName);
actualChanges.put(daysToDormancyParamName, newValue);
this.daysToDormancy = newValue;
}
if(command.isChangeInLongParameterNamed(daysToEscheatParamName, this.daysToEscheat)){
final Long newValue = command.longValueOfParameterNamed(daysToEscheatParamName);
actualChanges.put(daysToEscheatParamName, newValue);
this.daysToEscheat = newValue;
}
if(!this.isDormancyTrackingActive){
this.daysToInactive = null;
this.daysToDormancy = null;
this.daysToEscheat = null;
}
validateLockinDetails(); validateLockinDetails();
esnureOverdraftLimitsSetForOverdraftAccounts(); esnureOverdraftLimitsSetForOverdraftAccounts();
@@ -676,4 +730,20 @@ public class SavingsProduct extends AbstractPersistable<Long> {
return this.withHoldTax; return this.withHoldTax;
} }
public boolean isDormancyTrackingActive() {
return null == this.isDormancyTrackingActive? false: this.isDormancyTrackingActive;
}
public Long getDaysToInactive() {
return this.daysToInactive;
}
public Long getDaysToDormancy() {
return this.daysToDormancy;
}
public Long getDaysToEscheat() {
return this.daysToEscheat;
}
} }
@@ -44,6 +44,10 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraf
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName; 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.taxGroupIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToInactiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToDormancyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToEscheatParamName;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.HashSet; import java.util.HashSet;
@@ -174,11 +178,17 @@ public class SavingsProductAssembler {
boolean withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); boolean withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName);
final TaxGroup taxGroup = assembleTaxGroup(command); final TaxGroup taxGroup = assembleTaxGroup(command);
final Boolean isDormancyTrackingActive = command.booleanObjectValueOfParameterNamed(isDormancyTrackingActiveParamName);
final Long daysToInactive = command.longValueOfParameterNamed(daysToInactiveParamName);
final Long daysToDormancy = command.longValueOfParameterNamed(daysToDormancyParamName);
final Long daysToEscheat = command.longValueOfParameterNamed(daysToEscheatParamName);
return SavingsProduct.createNew(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, return SavingsProduct.createNew(name, shortName, description, currency, interestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, accountingRuleType, charges, lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, accountingRuleType, charges,
allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, minBalanceForInterestCalculation, allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, minBalanceForInterestCalculation,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup); nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup,
isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
public Set<Charge> assembleListOfSavingsProductCharges(final JsonCommand command, final String savingsProductCurrencyCode) { public Set<Charge> assembleListOfSavingsProductCharges(final JsonCommand command, final String savingsProductCurrencyCode) {
@@ -19,12 +19,14 @@
package org.apache.fineract.portfolio.savings.service; package org.apache.fineract.portfolio.savings.service;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.data.SavingsAccountData; import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
import org.joda.time.LocalDate;
public interface SavingsAccountReadPlatformService { public interface SavingsAccountReadPlatformService {
@@ -50,4 +52,10 @@ public interface SavingsAccountReadPlatformService {
SavingsAccountTransactionData retrieveSavingsTransaction(Long savingsId, Long transactionId, DepositAccountType depositAccountType); SavingsAccountTransactionData retrieveSavingsTransaction(Long savingsId, Long transactionId, DepositAccountType depositAccountType);
Collection<SavingsAccountData> retrieveForLookup(Long clientId, Boolean overdraft); Collection<SavingsAccountData> retrieveForLookup(Long clientId, Boolean overdraft);
List<Long> retrieveSavingsIdsPendingInactive(LocalDate tenantLocalDate);
List<Long> retrieveSavingsIdsPendingDormant(LocalDate tenantLocalDate);
List<Long> retrieveSavingsIdsPendingEscheat(LocalDate tenantLocalDate);
} }
@@ -24,6 +24,8 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
@@ -38,6 +40,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.staff.data.StaffData; import org.apache.fineract.organisation.staff.data.StaffData;
import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
import org.apache.fineract.portfolio.account.data.AccountTransferData; import org.apache.fineract.portfolio.account.data.AccountTransferData;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
import org.apache.fineract.portfolio.client.data.ClientData; import org.apache.fineract.portfolio.client.data.ClientData;
@@ -56,15 +59,22 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimel
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountData; import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData; import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData; import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
import org.apache.fineract.portfolio.savings.data.SavingsProductData; import org.apache.fineract.portfolio.savings.data.SavingsProductData;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountSubStatusEnum;
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException; import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
import org.apache.fineract.portfolio.tax.data.TaxGroupData; import org.apache.fineract.portfolio.tax.data.TaxGroupData;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
import org.joda.time.Days;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
@@ -82,6 +92,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
private final StaffReadPlatformService staffReadPlatformService; private final StaffReadPlatformService staffReadPlatformService;
private final SavingsDropdownReadPlatformService dropdownReadPlatformService; private final SavingsDropdownReadPlatformService dropdownReadPlatformService;
private final ChargeReadPlatformService chargeReadPlatformService; private final ChargeReadPlatformService chargeReadPlatformService;
private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
// mappers // mappers
private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper; private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper;
@@ -217,7 +228,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("sp.id as productId, sp.name as productName, "); sqlBuilder.append("sp.id as productId, sp.name as productName, ");
sqlBuilder.append("s.id fieldOfficerId, s.display_name as fieldOfficerName, "); sqlBuilder.append("s.id fieldOfficerId, s.display_name as fieldOfficerName, ");
sqlBuilder.append("sa.status_enum as statusEnum, "); sqlBuilder.append("sa.status_enum as statusEnum, ");
sqlBuilder.append("sa.sub_status_enum as subStatusEnum, ");
sqlBuilder.append("sa.submittedon_date as submittedOnDate,"); sqlBuilder.append("sa.submittedon_date as submittedOnDate,");
sqlBuilder.append("sbu.username as submittedByUsername,"); sqlBuilder.append("sbu.username as submittedByUsername,");
sqlBuilder.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,"); sqlBuilder.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,");
@@ -282,8 +293,16 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
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.withhold_tax as withHoldTax,");
sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax,"); sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax,");
sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, ");
sqlBuilder.append("(select IFNULL(max(sat.transaction_date),sa.activatedon_date) ");
sqlBuilder.append("from m_savings_account_transaction as sat ");
sqlBuilder.append("where sat.is_reversed = 0 ");
sqlBuilder.append("and sat.transaction_type_enum in (1,2) ");
sqlBuilder.append("and sat.savings_account_id = sa.id) as lastActiveTransactionDate, ");
sqlBuilder.append("sp.is_dormancy_tracking_active as isDormancyTrackingActive, ");
sqlBuilder.append("sp.days_to_inactive as daysToInactive, ");
sqlBuilder.append("sp.days_to_dormancy as daysToDormancy, ");
sqlBuilder.append("sp.days_to_escheat as daysToEscheat ");
sqlBuilder.append("from m_savings_account sa "); sqlBuilder.append("from m_savings_account sa ");
sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id "); 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 "); sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
@@ -328,6 +347,31 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum"); final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum); final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum);
final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum");
final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum);
final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate");
final boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive");
final Integer numDaysToInactive = JdbcSupport.getInteger(rs, "daysToInactive");
final Integer numDaysToDormancy = JdbcSupport.getInteger(rs, "daysToDormancy");
final Integer numDaysToEscheat = JdbcSupport.getInteger(rs, "daysToEscheat");
Integer daysToInactive = null;
Integer daysToDormancy = null;
Integer daysToEscheat = null;
LocalDate localTenantDate = DateUtils.getLocalDateOfTenant();
if(isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())){
if(subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()){
daysToEscheat = Days.daysBetween(localTenantDate,lastActiveTransactionDate.plusDays(numDaysToEscheat)).getDays();
}
if(subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()){
daysToDormancy = Days.daysBetween(localTenantDate,lastActiveTransactionDate.plusDays(numDaysToDormancy)).getDays();
}
if(subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()){
daysToInactive = Days.daysBetween(localTenantDate,lastActiveTransactionDate.plusDays(numDaysToInactive)).getDays();
}
}
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
final String submittedByUsername = rs.getString("submittedByUsername"); final String submittedByUsername = rs.getString("submittedByUsername");
final String submittedByFirstname = rs.getString("submittedByFirstname"); final String submittedByFirstname = rs.getString("submittedByFirstname");
@@ -469,11 +513,12 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
} }
return SavingsAccountData.instance(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, return SavingsAccountData.instance(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency,
interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers,
allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, summary, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData); minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax,
taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
} }
@@ -997,14 +1042,22 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final SavingsAccountSummaryData summary = null; final SavingsAccountSummaryData summary = null;
final BigDecimal onHoldFunds = null; final BigDecimal onHoldFunds = null;
final SavingsAccountSubStatusEnumData subStatus = null;
final LocalDate lastActiveTransactionDate = null;
final boolean isDormancyTrackingActive = false;
final Integer daysToInactive = null;
final Integer daysToDormancy = null;
final Integer daysToEscheat = null;
final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault(); final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault();
final EnumOptionData depositType = null; final EnumOptionData depositType = null;
return SavingsAccountData.instance(null, null, depositType, null, groupId, groupName, clientId, clientName, productId, return SavingsAccountData.instance(null, null, depositType, null, groupId, groupName, clientId, clientName, productId,
productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualIterestRate, productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency,
interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, nominalAnnualIterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers,
allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, summary, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData); minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax,
taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
} }
@@ -1025,6 +1078,84 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
} }
@Override
public List<Long> retrieveSavingsIdsPendingInactive(
LocalDate tenantLocalDate) {
List<Long> ret = null;
StringBuilder sql = new StringBuilder("select sa.id ");
sql.append(" from m_savings_account as sa ");
sql.append(" inner join m_savings_product as sp on (sa.product_id = sp.id and sp.is_dormancy_tracking_active = 1) ");
sql.append(" where sa.status_enum = 300 ");
sql.append(" and sa.sub_status_enum = 0 ");
sql.append(" and DATEDIFF(?,(select IFNULL(max(sat.transaction_date),sa.activatedon_date) ");
sql.append(" from m_savings_account_transaction as sat ");
sql.append(" where sat.is_reversed = 0 ");
sql.append(" and sat.transaction_type_enum in (1,2) ");
sql.append(" and sat.savings_account_id = sa.id)) >= sp.days_to_inactive ");
try {
ret = this.jdbcTemplate.queryForList(sql.toString(), Long.class, new Object[] {formatter.print(tenantLocalDate)});
} catch (EmptyResultDataAccessException e) {
// ignore empty result scenario
} catch (DataAccessException e) {
throw e;
}
return ret;
}
@Override
public List<Long> retrieveSavingsIdsPendingDormant(
LocalDate tenantLocalDate) {
List<Long> ret = null;
StringBuilder sql = new StringBuilder("select sa.id ");
sql.append(" from m_savings_account as sa ");
sql.append(" inner join m_savings_product as sp on (sa.product_id = sp.id and sp.is_dormancy_tracking_active = 1) ");
sql.append(" where sa.status_enum = 300 ");
sql.append(" and sa.sub_status_enum = 100 ");
sql.append(" and DATEDIFF(?,(select IFNULL(max(sat.transaction_date),sa.activatedon_date) ");
sql.append(" from m_savings_account_transaction as sat ");
sql.append(" where sat.is_reversed = 0 ");
sql.append(" and sat.transaction_type_enum in (1,2) ");
sql.append(" and sat.savings_account_id = sa.id)) >= sp.days_to_dormancy ");
try {
ret = this.jdbcTemplate.queryForList(sql.toString(), Long.class, new Object[] {formatter.print(tenantLocalDate)});
} catch (EmptyResultDataAccessException e) {
// ignore empty result scenario
} catch (DataAccessException e) {
throw e;
}
return ret;
}
@Override
public List<Long> retrieveSavingsIdsPendingEscheat(
LocalDate tenantLocalDate) {
List<Long> ret = null;
StringBuilder sql = new StringBuilder("select sa.id ");
sql.append(" from m_savings_account as sa ");
sql.append(" inner join m_savings_product as sp on (sa.product_id = sp.id and sp.is_dormancy_tracking_active = 1) ");
sql.append(" where sa.status_enum = 300 ");
sql.append(" and sa.sub_status_enum = 200 ");
sql.append(" and DATEDIFF(?,(select IFNULL(max(sat.transaction_date),sa.activatedon_date) ");
sql.append(" from m_savings_account_transaction as sat ");
sql.append(" where sat.is_reversed = 0 ");
sql.append(" and sat.transaction_type_enum in (1,2) ");
sql.append(" and sat.savings_account_id = sa.id)) >= sp.days_to_escheat ");
try {
ret = this.jdbcTemplate.queryForList(sql.toString(), Long.class, new Object[] {formatter.print(tenantLocalDate)});
} catch (EmptyResultDataAccessException e) {
// ignore empty result scenario
} catch (DataAccessException e) {
throw e;
}
return ret;
}
/* /*
* private static final class SavingsAccountAnnualFeeMapper implements * private static final class SavingsAccountAnnualFeeMapper implements
* RowMapper<SavingsAccountAnnualFeeData> { * RowMapper<SavingsAccountAnnualFeeData> {
@@ -81,4 +81,10 @@ public interface SavingsAccountWritePlatformService {
void postInterest(SavingsAccount account); void postInterest(SavingsAccount account);
CommandProcessingResult modifyWithHoldTax(Long savingsAccountId, JsonCommand command); CommandProcessingResult modifyWithHoldTax(Long savingsAccountId, JsonCommand command);
void setSubStatusInactive(Long savingsId);
void setSubStatusDormant(Long savingsId);
void escheat(Long savingsId);
} }
@@ -78,6 +78,7 @@ import org.apache.fineract.portfolio.savings.exception.SavingsOfficerAssignmentE
import org.apache.fineract.portfolio.savings.exception.SavingsOfficerUnassignmentException; import org.apache.fineract.portfolio.savings.exception.SavingsOfficerUnassignmentException;
import org.apache.fineract.portfolio.savings.exception.TransactionUpdateNotAllowedException; import org.apache.fineract.portfolio.savings.exception.TransactionUpdateNotAllowedException;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatter;
@@ -130,6 +131,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
private final WorkingDaysRepositoryWrapper workingDaysRepository; private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository; private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository;
private final AppUserRepositoryWrapper appuserRepository;
@Autowired @Autowired
public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -148,7 +150,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository, final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository,
final SavingsAccountDataValidator fromApiJsonDeserializer, final SavingsAccountRepositoryWrapper savingsRepository, final SavingsAccountDataValidator fromApiJsonDeserializer, final SavingsAccountRepositoryWrapper savingsRepository,
final StaffRepositoryWrapper staffRepository, final ConfigurationDomainService configurationDomainService, final StaffRepositoryWrapper staffRepository, final ConfigurationDomainService configurationDomainService,
final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository) { final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository,
final AppUserRepositoryWrapper appuserRepository) {
this.context = context; this.context = context;
this.savingAccountRepository = savingAccountRepository; this.savingAccountRepository = savingAccountRepository;
this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
@@ -171,6 +174,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.staffRepository = staffRepository; this.staffRepository = staffRepository;
this.configurationDomainService = configurationDomainService; this.configurationDomainService = configurationDomainService;
this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository; this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository;
this.appuserRepository = appuserRepository;
} }
@Transactional @Transactional
@@ -1223,7 +1227,39 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.build(); .build();
} }
private AppUser getAppUserIfPresent() { @Override
@Transactional
public void setSubStatusInactive(Long savingsId){
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.setSubStatusInactive(appuserRepository.fetchSystemUser());
this.savingAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
}
@Override
@Transactional
public void setSubStatusDormant(Long savingsId){
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
account.setSubStatusDormant();
this.savingAccountRepository.save(account);
}
@Override
@Transactional
public void escheat(Long savingsId){
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.escheat(appuserRepository.fetchSystemUser());
this.savingAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
}
private AppUser getAppUserIfPresent() {
AppUser user = null; AppUser user = null;
if (this.context != null) { if (this.context != null) {
user = this.context.getAuthenticatedUserIfPresent(); user = this.context.getAuthenticatedUserIfPresent();
@@ -36,8 +36,10 @@ import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsWithdrawalFeesType; import org.apache.fineract.portfolio.savings.SavingsWithdrawalFeesType;
import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData; import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountSubStatusEnum;
public class SavingsEnumerations { public class SavingsEnumerations {
@@ -187,6 +189,10 @@ public class SavingsEnumerations {
optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.DIVIDEND_PAYOUT.getValue().longValue(), optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.DIVIDEND_PAYOUT.getValue().longValue(),
SavingsAccountTransactionType.DIVIDEND_PAYOUT.getCode(), "Dividend Payout"); SavingsAccountTransactionType.DIVIDEND_PAYOUT.getCode(), "Dividend Payout");
break; break;
case ESCHEAT:
optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.ESCHEAT.getValue().longValue(),
SavingsAccountTransactionType.ESCHEAT.getCode(), "Escheat");
break;
} }
return optionData; return optionData;
} }
@@ -278,6 +284,39 @@ public class SavingsEnumerations {
return optionData; return optionData;
} }
public static SavingsAccountSubStatusEnumData subStatus(final Integer subStatusEnum) {
return subStatus(SavingsAccountSubStatusEnum.fromInt(subStatusEnum));
}
public static SavingsAccountSubStatusEnumData subStatus(final SavingsAccountSubStatusEnum type) {
final boolean none = type.isSubStatusNone();
final boolean inactive = type.isSubStatusInactive();
final boolean dormant = type.isSubStatusDormant();
final boolean escheat = type.isSubStatusEscheat();
SavingsAccountSubStatusEnumData optionData = new SavingsAccountSubStatusEnumData(SavingsAccountSubStatusEnum.NONE.getValue().longValue(),
SavingsAccountSubStatusEnum.NONE.getCode(), "None", true, inactive, dormant, escheat);
switch (type) {
case INACTIVE:
optionData = new SavingsAccountSubStatusEnumData(SavingsAccountSubStatusEnum.INACTIVE.getValue().longValue(),
SavingsAccountSubStatusEnum.INACTIVE.getCode(), "Inactive", none, inactive, dormant, escheat);
break;
case DORMANT:
optionData = new SavingsAccountSubStatusEnumData(SavingsAccountSubStatusEnum.DORMANT.getValue().longValue(),
SavingsAccountSubStatusEnum.DORMANT.getCode(), "Dormant", none, inactive, dormant, escheat);
break;
case ESCHEAT:
optionData = new SavingsAccountSubStatusEnumData(SavingsAccountSubStatusEnum.ESCHEAT.getValue().longValue(),
SavingsAccountSubStatusEnum.ESCHEAT.getCode(), "Escheat", none, inactive, dormant, escheat);
break;
default:
break;
}
return optionData;
}
public static EnumOptionData interestPostingPeriodType(final Integer type) { public static EnumOptionData interestPostingPeriodType(final Integer type) {
return interestPostingPeriodType(SavingsPostingInterestPeriodType.fromInt(type)); return interestPostingPeriodType(SavingsPostingInterestPeriodType.fromInt(type));
} }
@@ -134,7 +134,11 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
sqlBuilder.append("sp.min_balance_for_interest_calculation as minBalanceForInterestCalculation,"); 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("sp.withhold_tax as withHoldTax,");
sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, ");
sqlBuilder.append("sp.is_dormancy_tracking_active as isDormancyTrackingActive,");
sqlBuilder.append("sp.days_to_inactive as daysToInactive,");
sqlBuilder.append("sp.days_to_dormancy as daysToDormancy,");
sqlBuilder.append("sp.days_to_escheat as daysToEscheat ");
sqlBuilder.append("from m_savings_product sp "); sqlBuilder.append("from m_savings_product sp ");
sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); 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 "); sqlBuilder.append("left join m_tax_group tg on tg.id = sp.tax_group_id ");
@@ -211,12 +215,17 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName); taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName);
} }
final Boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive");
final Long daysToInactive = JdbcSupport.getLong(rs, "daysToInactive");
final Long daysToDormancy = JdbcSupport.getLong(rs, "daysToDormancy");
final Long daysToEscheat = JdbcSupport.getLong(rs, "daysToEscheat");
return SavingsProductData.instance(id, name, shortName, description, currency, nominalAnnualInterestRate, return SavingsProductData.instance(id, name, shortName, description, currency, nominalAnnualInterestRate,
compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers,
accountingRuleType, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, accountingRuleType, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax,
taxGroupData); taxGroupData, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
} }
} }
@@ -145,11 +145,11 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi
try { try {
this.context.authenticatedUser(); this.context.authenticatedUser();
this.fromApiJsonDataValidator.validateForUpdate(command.json());
final SavingsProduct product = this.savingProductRepository.findOne(productId); final SavingsProduct product = this.savingProductRepository.findOne(productId);
if (product == null) { throw new SavingsProductNotFoundException(productId); } if (product == null) { throw new SavingsProductNotFoundException(productId); }
this.fromApiJsonDataValidator.validateForUpdate(command.json(), product);
final Map<String, Object> changes = product.update(command); final Map<String, Object> changes = product.update(command);
if (changes.containsKey(chargesParamName)) { if (changes.containsKey(chargesParamName)) {
@@ -25,4 +25,6 @@ public interface SavingsSchedularService {
void postInterestForAccounts() throws JobExecutionException; void postInterestForAccounts() throws JobExecutionException;
void updateSavingsDormancyStatus() throws JobExecutionException;
} }
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.savings.service;
import java.util.List; import java.util.List;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.jobs.annotation.CronTarget; import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
import org.apache.fineract.infrastructure.jobs.service.JobName; import org.apache.fineract.infrastructure.jobs.service.JobName;
@@ -27,6 +28,7 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository; import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -36,14 +38,17 @@ public class SavingsSchedularServiceImpl implements SavingsSchedularService {
private final SavingsAccountAssembler savingAccountAssembler; private final SavingsAccountAssembler savingAccountAssembler;
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService; private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
private final SavingsAccountRepository savingAccountRepository; private final SavingsAccountRepository savingAccountRepository;
private final SavingsAccountReadPlatformService savingAccountReadPlatformService;
@Autowired @Autowired
public SavingsSchedularServiceImpl(final SavingsAccountAssembler savingAccountAssembler, public SavingsSchedularServiceImpl(final SavingsAccountAssembler savingAccountAssembler,
final SavingsAccountWritePlatformService savingsAccountWritePlatformService, final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
final SavingsAccountRepository savingAccountRepository) { final SavingsAccountRepository savingAccountRepository,
final SavingsAccountReadPlatformService savingAccountReadPlatformService) {
this.savingAccountAssembler = savingAccountAssembler; this.savingAccountAssembler = savingAccountAssembler;
this.savingsAccountWritePlatformService = savingsAccountWritePlatformService; this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
this.savingAccountRepository = savingAccountRepository; this.savingAccountRepository = savingAccountRepository;
this.savingAccountReadPlatformService = savingAccountReadPlatformService;
} }
@CronTarget(jobName = JobName.POST_INTEREST_FOR_SAVINGS) @CronTarget(jobName = JobName.POST_INTEREST_FOR_SAVINGS)
@@ -68,4 +73,34 @@ public class SavingsSchedularServiceImpl implements SavingsSchedularService {
if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); } if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
} }
@CronTarget(jobName = JobName.UPDATE_SAVINGS_DORMANT_ACCOUNTS)
@Override
public void updateSavingsDormancyStatus() throws JobExecutionException {
final LocalDate tenantLocalDate = DateUtils.getLocalDateOfTenant();
final List<Long> savingsPendingInactive = this.savingAccountReadPlatformService
.retrieveSavingsIdsPendingInactive(tenantLocalDate);
if(null != savingsPendingInactive && savingsPendingInactive.size() > 0){
for(Long savingsId : savingsPendingInactive){
this.savingsAccountWritePlatformService.setSubStatusInactive(savingsId);
}
}
final List<Long> savingsPendingDormant = this.savingAccountReadPlatformService
.retrieveSavingsIdsPendingDormant(tenantLocalDate);
if(null != savingsPendingDormant && savingsPendingDormant.size() > 0){
for(Long savingsId : savingsPendingDormant){
this.savingsAccountWritePlatformService.setSubStatusDormant(savingsId);
}
}
final List<Long> savingsPendingEscheat = this.savingAccountReadPlatformService
.retrieveSavingsIdsPendingEscheat(tenantLocalDate);
if(null != savingsPendingEscheat && savingsPendingEscheat.size() > 0){
for(Long savingsId : savingsPendingEscheat){
this.savingsAccountWritePlatformService.escheat(savingsId);
}
}
}
} }
@@ -0,0 +1,18 @@
ALTER TABLE `m_savings_product`
ADD COLUMN `is_dormancy_tracking_active` SMALLINT(1) NULL,
ADD COLUMN `days_to_inactive` INT(11) NULL,
ADD COLUMN `days_to_dormancy` INT(11) NULL,
ADD COLUMN `days_to_escheat` INT(11) NULL;
ALTER TABLE `m_savings_account`
ADD COLUMN `sub_status_enum` SMALLINT(5) NOT NULL DEFAULT '0' AFTER `status_enum`;
INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`) VALUES ('Update Savings Dormant Accounts', 'Update Savings Dormant Accounts', '0 0 0 1/1 * ? *', now());
INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_category`, `report_sql`, `description`, `core_report`, `use_report`) VALUES ('Savings Accounts Dormancy Report', 'Table', 'Savings', 'select cl.display_name as \'Client Display Name\',\r\nsa.account_no as \'Account Number\',\r\ncl.mobile_no as \'Mobile Number\',\r\n@lastdate:=(select IFNULL(max(sat.transaction_date),sa.activatedon_date) \r\n from m_savings_account_transaction as sat \r\n where sat.is_reversed = 0 \r\n and sat.transaction_type_enum in (1,2) \r\n and sat.savings_account_id = sa.id) as \'Date of Last Activity\',\r\nDATEDIFF(now(), @lastdate) as \'Days Since Last Activity\'\r\nfrom m_savings_account as sa \r\ninner join m_savings_product as sp on (sa.product_id = sp.id and sp.is_dormancy_tracking_active = 1) \r\nleft join m_client as cl on sa.client_id = cl.id \r\nwhere sa.sub_status_enum = ${subStatus}\r\nand cl.office_id = ${officeId}', NULL, 1, 1);
INSERT INTO `stretchy_parameter` (`parameter_name`, `parameter_variable`, `parameter_label`, `parameter_displayType`, `parameter_FormatType`, `parameter_default`, `parameter_sql`) VALUES ('SavingsAccountSubStatus', 'subStatus', 'SavingsAccountDormancyStatus', 'select', 'number', '100', 'select * from\r\n(select 100 as id, "Inactive" as name union all\r\nselect 200 as id, "Dormant" as name union all \r\nselect 300 as id, "Escheat" as name) x\r\norder by x.`id`');
INSERT INTO stretchy_report_parameter (report_id, parameter_id) VALUES ((select sr.id From stretchy_report sr where sr.report_name='Savings Accounts Dormancy Report'),(select sp.id from stretchy_parameter sp where sp.parameter_name='SavingsAccountSubStatus'));
INSERT INTO stretchy_report_parameter (report_id, parameter_id) VALUES ((select sr.id From stretchy_report sr where sr.report_name='Savings Accounts Dormancy Report'),(select sp.id from stretchy_parameter sp where sp.parameter_name='OfficeIdSelectOne'));