MIFOSX-1850 : Recurring deposits without maturity date

This commit is contained in:
Pramod Nuthakki
2015-02-24 10:45:10 +05:30
parent 7ba9a81e53
commit 4a2a85d20e
12 changed files with 274 additions and 87 deletions
@@ -23,7 +23,8 @@ public enum JobName {
UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS("Update Deposit Accounts Maturity details"),
TRANSFER_INTEREST_TO_SAVINGS("Transfer Interest To Savings"),
ADD_PERIODIC_ACCRUAL_ENTRIES("Add Periodic Accrual Transactions"),
RECALCULATE_INTEREST_FOR_LOAN("Recalculate Interest For Loans");
RECALCULATE_INTEREST_FOR_LOAN("Recalculate Interest For Loans"),
GENERATE_RD_SCEHDULE("Generate Mandatory Savings Schedule");
private final String name;
@@ -0,0 +1,47 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.savings;
import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.calendar.domain.CalendarFrequencyType;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
public class DepositAccountUtils {
public static final int GENERATE_MINIMUM_NUMBER_OF_FUTURE_INSTALMENTS = 5;
public static LocalDate calculateNextDepositDate(final LocalDate lastDepositDate, final PeriodFrequencyType frequency,
final int recurringEvery) {
LocalDate nextDepositDate = lastDepositDate;
switch (frequency) {
case DAYS:
nextDepositDate = lastDepositDate.plusDays(recurringEvery);
break;
case WEEKS:
nextDepositDate = lastDepositDate.plusWeeks(recurringEvery);
break;
case MONTHS:
nextDepositDate = lastDepositDate.plusMonths(recurringEvery);
break;
case YEARS:
nextDepositDate = lastDepositDate.plusYears(recurringEvery);
break;
case INVALID:
break;
}
return nextDepositDate;
}
public static LocalDate calculateNextDepositDate(final LocalDate lastDepositDate, final String recurrence) {
final PeriodFrequencyType frequencyType = CalendarFrequencyType.from(CalendarUtils.getFrequency(recurrence));
Integer frequency = CalendarUtils.getInterval(recurrence);
frequency = frequency == -1 ? 1 : frequency;
return calculateNextDepositDate(lastDepositDate, frequencyType, frequency);
}
}
@@ -125,7 +125,7 @@ public class DepositAccountDataValidator {
validateDepositDetailsForUpdate(element, baseDataValidator);
validatePreClosureDetailForUpdate(element, baseDataValidator);
validateDepositTermDeatilForUpdate(element, baseDataValidator);
validateDepositTermDeatilForUpdate(element, baseDataValidator, DepositAccountType.FIXED_DEPOSIT);
// validateSavingsCharges(element, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
@@ -166,7 +166,7 @@ public class DepositAccountDataValidator {
validateDepositDetailsForUpdate(element, baseDataValidator);
validatePreClosureDetailForUpdate(element, baseDataValidator);
validateDepositTermDeatilForUpdate(element, baseDataValidator);
validateDepositTermDeatilForUpdate(element, baseDataValidator, DepositAccountType.RECURRING_DEPOSIT);
validateRecurringDetailForUpdate(element, baseDataValidator);
// validateSavingsCharges(element, baseDataValidator);
@@ -494,8 +494,10 @@ public class DepositAccountDataValidator {
baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount();
}
final Integer depositPeriod = fromApiJsonHelper.extractIntegerSansLocaleNamed(depositPeriodParamName, element);
baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull().integerGreaterThanZero();
if (depositType.isFixedDeposit() || fromApiJsonHelper.parameterExists(depositPeriodParamName, element)) {
final Integer depositPeriod = fromApiJsonHelper.extractIntegerSansLocaleNamed(depositPeriodParamName, element);
baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull().integerGreaterThanZero();
}
final Integer depositPeriodFrequencyId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(depositPeriodFrequencyIdParamName,
element);
@@ -503,7 +505,8 @@ public class DepositAccountDataValidator {
.isOneOfTheseValues(SavingsPeriodFrequencyType.integerValues());
}
private void validateDepositTermDeatilForUpdate(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
private void validateDepositTermDeatilForUpdate(final JsonElement element, final DataValidatorBuilder baseDataValidator,
final DepositAccountType depositAccountType) {
this.productDataValidator.validateDepositTermDetailForUpdate(element, baseDataValidator);
if (fromApiJsonHelper.parameterExists(depositAmountParamName, element)) {
@@ -513,7 +516,11 @@ public class DepositAccountDataValidator {
if (fromApiJsonHelper.parameterExists(depositPeriodParamName, element)) {
final Integer depositPeriod = fromApiJsonHelper.extractIntegerSansLocaleNamed(depositPeriodParamName, element);
baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull().integerGreaterThanZero();
if (depositAccountType.isFixedDeposit()) {
baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull().integerGreaterThanZero();
} else {
baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull().integerGreaterThanZero();
}
}
if (fromApiJsonHelper.parameterExists(depositPeriodFrequencyIdParamName, element)) {
@@ -221,7 +221,7 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
Long savingsTransactionId = null;
account.postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
account.postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, closedDate);
final BigDecimal transactionAmount = account.getAccountBalance();
final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName);
final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
@@ -195,6 +195,11 @@ public class DepositAccountTermAndPreClosure extends AbstractPersistable<Long> {
this.maturityAmount = this.depositAmount.add(interestPayable);
this.maturityDate = maturityDate.toDate();
}
public void updateDepositAmount(final BigDecimal depositAmount) {
this.depositAmount = depositAmount;
}
public LocalDate getMaturityLocalDate() {
LocalDate maturityLocalDate = null;
@@ -49,6 +49,7 @@ import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.interestratechart.domain.InterestRateChart;
import org.mifosplatform.portfolio.savings.DepositAccountOnClosureType;
import org.mifosplatform.portfolio.savings.DepositAccountUtils;
import org.mifosplatform.portfolio.savings.DepositsApiConstants;
import org.mifosplatform.portfolio.savings.PreClosurePenalInterestOnType;
import org.mifosplatform.portfolio.savings.SavingsApiConstants;
@@ -192,6 +193,10 @@ public class RecurringDepositAccount extends SavingsAccount {
}
}
if (depositCloseDate == null) {
depositCloseDate = LocalDate.now();
}
final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount();
BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate,
this.client);
@@ -209,8 +214,16 @@ public class RecurringDepositAccount extends SavingsAccount {
public void updateMaturityDateAndAmount(final MathContext mc, final boolean isPreMatureClosure,
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
final LocalDate maturityDate = calculateMaturityDate();
final LocalDate interestCalculationUpto = maturityDate.minusDays(1);
final List<SavingsAccountTransaction> allTransactions = generateIncludingFutureTransactions(interestCalculationUpto);
LocalDate interestCalculationUpto = null;
List<SavingsAccountTransaction> allTransactions = null;
if (maturityDate == null) {
interestCalculationUpto = DateUtils.getLocalDateOfTenant();
allTransactions = getTransactions(interestCalculationUpto, false);
} else {
interestCalculationUpto = maturityDate.minusDays(1);
allTransactions = getTransactions(interestCalculationUpto, true);
}
final List<PostingPeriod> postingPeriods = calculateInterestPayable(mc, interestCalculationUpto, allTransactions,
isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
@@ -220,8 +233,12 @@ public class RecurringDepositAccount extends SavingsAccount {
totalInterestPayable = totalInterestPayable.plus(postingPeriod.getInterestEarned());
totalDepositAmount = totalDepositAmount.plus(postingPeriod.closingBalance()).minus(postingPeriod.openingBalance());
}
this.accountTermAndPreClosure.updateMaturityDetails(totalDepositAmount.getAmount(), totalInterestPayable.getAmount(), maturityDate);
if (maturityDate == null) {
this.accountTermAndPreClosure.updateDepositAmount(totalDepositAmount.getAmount());
} else {
this.accountTermAndPreClosure.updateMaturityDetails(totalDepositAmount.getAmount(), totalInterestPayable.getAmount(),
maturityDate);
}
}
public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
@@ -239,7 +256,7 @@ public class RecurringDepositAccount extends SavingsAccount {
if (!this.maturityDate().isAfter(todayDate)) {
// update account status
this.status = SavingsAccountStatusType.MATURED.getValue();
postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, todayDate);
}
}
@@ -248,6 +265,7 @@ public class RecurringDepositAccount extends SavingsAccount {
final LocalDate startDate = depositStartDate();
LocalDate maturityDate = null;
final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod();
if (depositPeriod == null) { return maturityDate; }
switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) {
case DAYS:
maturityDate = startDate.plusDays(depositPeriod);
@@ -315,16 +333,18 @@ public class RecurringDepositAccount extends SavingsAccount {
return allPostingPeriods;
}
private List<SavingsAccountTransaction> generateIncludingFutureTransactions(final LocalDate depositEndDate) {
private List<SavingsAccountTransaction> getTransactions(final LocalDate depositEndDate, final boolean generateFutureTransactions) {
List<SavingsAccountTransaction> allTransactions = new ArrayList<>();
// add existing transactions
allTransactions.addAll(retreiveOrderedNonInterestPostingTransactions());
for (RecurringDepositScheduleInstallment installment : depositScheduleInstallments()) {
if (installment.isPrincipalNotCompleted(getCurrency())) {
final SavingsAccountTransaction transaction = SavingsAccountTransaction
.deposit(null, office(), null, installment.dueDate(), installment.getDepositAmountOutstanding(getCurrency()),
installment.dueDate().toDate(), null);
allTransactions.add(transaction);
if (generateFutureTransactions) {
for (RecurringDepositScheduleInstallment installment : depositScheduleInstallments()) {
if (installment.isPrincipalNotCompleted(getCurrency())) {
final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null,
installment.dueDate(), installment.getDepositAmountOutstanding(getCurrency()), installment.dueDate().toDate(),
null);
allTransactions.add(transaction);
}
}
}
@@ -358,29 +378,6 @@ public class RecurringDepositAccount extends SavingsAccount {
return allTransactions;
}
private LocalDate calculateNextDepositDate(final LocalDate lastDepositDate, final PeriodFrequencyType frequency,
final int recurringEvery) {
LocalDate nextDepositDate = lastDepositDate;
switch (frequency) {
case DAYS:
nextDepositDate = lastDepositDate.plusDays(recurringEvery);
break;
case WEEKS:
nextDepositDate = lastDepositDate.plusWeeks(recurringEvery);
break;
case MONTHS:
nextDepositDate = lastDepositDate.plusMonths(recurringEvery);
break;
case YEARS:
nextDepositDate = lastDepositDate.plusYears(recurringEvery);
break;
case INVALID:
break;
}
return nextDepositDate;
}
public LocalDate depositStartDate() {
final LocalDate depositStartDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate();
if (depositStartDate == null) return accountSubmittedOrActivationDate();
@@ -500,7 +497,7 @@ public class RecurringDepositAccount extends SavingsAccount {
.resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction);
final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status);
if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus)) {
if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus) && this.maturityDate() != null) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.matured.state");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
@@ -514,7 +511,7 @@ public class RecurringDepositAccount extends SavingsAccount {
.failWithCode("must.be.after.activation.date");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
if (closedDate.isBefore(maturityDate())) {
if (maturityDate() != null && closedDate.isBefore(maturityDate())) {
baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate)
.failWithCode("must.be.after.account.maturity.date");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
@@ -564,11 +561,15 @@ public class RecurringDepositAccount extends SavingsAccount {
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
}
public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) {
final LocalDate interestPostingUpToDate = maturityDate();
public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate closeDate) {
LocalDate interestPostingUpToDate = maturityDate();
if (interestPostingUpToDate == null) {
interestPostingUpToDate = closeDate;
}
final MathContext mc = MathContext.DECIMAL64;
boolean isInterestTransfer = false;
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer,
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate.minusDays(1), isInterestTransfer,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
Money interestPostedToDate = Money.zero(this.currency);
@@ -895,7 +896,7 @@ public class RecurringDepositAccount extends SavingsAccount {
private boolean isBeforeMaturityDate(final LocalDate compareDate) {
final LocalDate maturityDate = this.maturityDate();
return compareDate.isBefore(maturityDate);
return maturityDate == null ? true : compareDate.isBefore(maturityDate);
}
private boolean isBeforeDepositStartDate(LocalDate compareDate) {
@@ -923,30 +924,35 @@ public class RecurringDepositAccount extends SavingsAccount {
}
final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod();
final SavingsPeriodFrequencyType depositPeriodFrequencyType = this.accountTermAndPreClosure.depositPeriodFrequencyType();
final boolean isValidDepositPeriod = this.accountTermAndPreClosure.depositTermDetail().isDepositBetweenMinAndMax(
depositStartDate(), calculateMaturityDate());
if (!isValidDepositPeriod) {
baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod)
.failWithCodeNoParameterAddedToErrorCode("deposit.period.not.between.min.and.max.deposit.term");
} else {
final Integer inMultiplesOf = this.accountTermAndPreClosure.depositTermDetail().inMultiplesOfDepositTerm();
if (inMultiplesOf != null) {
final boolean isValid = this.accountTermAndPreClosure.depositTermDetail().isValidInMultiplesOfPeriod(depositPeriod,
depositPeriodFrequencyType);
if (!isValid) {
baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod)
.failWithCodeNoParameterAddedToErrorCode("deposit.period.not.multiple.of.term");
if (this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm() != null) {
baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull();
}
if (depositPeriod != null) {
final SavingsPeriodFrequencyType depositPeriodFrequencyType = this.accountTermAndPreClosure.depositPeriodFrequencyType();
final boolean isValidDepositPeriod = this.accountTermAndPreClosure.depositTermDetail().isDepositBetweenMinAndMax(
depositStartDate(), calculateMaturityDate());
if (!isValidDepositPeriod) {
baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod)
.failWithCodeNoParameterAddedToErrorCode("deposit.period.not.between.min.and.max.deposit.term");
} else {
final Integer inMultiplesOf = this.accountTermAndPreClosure.depositTermDetail().inMultiplesOfDepositTerm();
if (inMultiplesOf != null) {
final boolean isValid = this.accountTermAndPreClosure.depositTermDetail().isValidInMultiplesOfPeriod(depositPeriod,
depositPeriodFrequencyType);
if (!isValid) {
baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod)
.failWithCodeNoParameterAddedToErrorCode("deposit.period.not.multiple.of.term");
}
}
}
}
if (isAccountLocked(calculateMaturityDate())) {
baseDataValidator
.reset()
.parameter(depositPeriodParamName)
.value(depositPeriod)
.failWithCode("deposit.period.must.be.greater.than.lock.in.period",
"Deposit period must be greater than account lock-in period.");
if (isAccountLocked(calculateMaturityDate())) {
baseDataValidator
.reset()
.parameter(depositPeriodParamName)
.value(depositPeriod)
.failWithCode("deposit.period.must.be.greater.than.lock.in.period",
"Deposit period must be greater than account lock-in period.");
}
}
if (firstDepositDateBeforeAccountSubmittedOrActivationDate()) {
@@ -969,8 +975,8 @@ public class RecurringDepositAccount extends SavingsAccount {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME);
if (this.chart != null) {
LocalDate maturityDate = calculateMaturityDate();
if (this.chart != null && maturityDate != null) {
final LocalDate chartFromDate = this.chart.getFromDateAsLocalDate();
LocalDate chartEndDate = this.chart.getEndDateAsLocalDate();
chartEndDate = chartEndDate == null ? DateUtils.getLocalDateOfTenant() : chartEndDate;
@@ -981,8 +987,8 @@ public class RecurringDepositAccount extends SavingsAccount {
}
final BigDecimal maturityAmount = this.accountTermAndPreClosure.maturityAmount();
BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(maturityAmount, depositStartDate(),
calculateMaturityDate(), this.client);
BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(maturityAmount, depositStartDate(), maturityDate,
this.client);
if (applicableInterestRate.equals(BigDecimal.ZERO)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
@@ -1088,18 +1094,28 @@ public class RecurringDepositAccount extends SavingsAccount {
installmentDate = depositStartDate();
}
final LocalDate maturityDate = calculateMaturityDate();
int installmentNumber = 1;
final LocalDate maturityDate = calcualteScheduleTillDate(frequency, recurringEvery);
final BigDecimal depositAmount = this.recurringDetail.mandatoryRecommendedDepositAmount();
while (maturityDate.isAfter(installmentDate)) {
final RecurringDepositScheduleInstallment installment = RecurringDepositScheduleInstallment.installment(this,
installmentNumber, installmentDate.toDate(), depositAmount);
depositScheduleInstallments.add(installment);
installmentDate = calculateNextDepositDate(installmentDate, frequency, recurringEvery);
installmentDate = DepositAccountUtils.calculateNextDepositDate(installmentDate, frequency, recurringEvery);
installmentNumber += 1;
}
}
private LocalDate calcualteScheduleTillDate(final PeriodFrequencyType frequency, final Integer recurringEvery) {
LocalDate tillDate = calculateMaturityDate();
if (tillDate == null) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
tillDate = DepositAccountUtils.calculateNextDepositDate(today, frequency, recurringEvery
* (DepositAccountUtils.GENERATE_MINIMUM_NUMBER_OF_FUTURE_INSTALMENTS + 1));
}
return tillDate;
}
private List<RecurringDepositScheduleInstallment> depositScheduleInstallments() {
if (this.depositScheduleInstallments == null) {
this.depositScheduleInstallments = new ArrayList<>();
@@ -1113,7 +1129,7 @@ public class RecurringDepositAccount extends SavingsAccount {
public void updateOverduePayments(final LocalDate todayDate) {
LocalDate overdueUptoDate = this.maturityDate();
if (overdueUptoDate.isAfter(todayDate)) {
if (overdueUptoDate == null || overdueUptoDate.isAfter(todayDate)) {
overdueUptoDate = todayDate;
}
@@ -6,6 +6,7 @@
package org.mifosplatform.portfolio.savings.service;
import java.util.Collection;
import java.util.Map;
import org.mifosplatform.infrastructure.core.data.PaginationParameters;
import org.mifosplatform.infrastructure.core.service.Page;
@@ -38,4 +39,6 @@ public interface DepositAccountReadPlatformService {
SavingsAccountTransactionData retrieveRecurringAccountDepositTransactionTemplate(final Long accountId);
Collection<AccountTransferDTO> retrieveDataForInterestTransfer();
Collection<Map<String, Object>> retriveDataForRDScheduleCreation();
}
@@ -10,14 +10,18 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
import org.mifosplatform.infrastructure.codes.service.CodeValueReadPlatformService;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.data.PaginationParameters;
import org.mifosplatform.infrastructure.core.data.PaginationParametersDataValidator;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.core.service.PaginationHelper;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
@@ -32,6 +36,7 @@ import org.mifosplatform.portfolio.account.domain.AccountTransferType;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarFrequencyType;
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
@@ -103,6 +108,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
private final RecurringAccountDepositTransactionTemplateMapper rdTransactionTemplateMapper;
private final DropdownReadPlatformService dropdownReadPlatformService;
private final CalendarReadPlatformService calendarReadPlatformService;
private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
@Autowired
public DepositAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
@@ -197,9 +203,13 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
sqlBuilder.append(this.depositAccountForMaturityRowMapper.schema());
sqlBuilder.append(" WHERE da.deposit_type_enum in (?, ?) and da.status_enum = ?");
return this.jdbcTemplate.query(sqlBuilder.toString(), this.depositAccountForMaturityRowMapper, new Object[] {
DepositAccountType.FIXED_DEPOSIT.getValue(), DepositAccountType.RECURRING_DEPOSIT.getValue(),
SavingsAccountStatusType.ACTIVE.getValue() });
LocalDate today = DateUtils.getLocalDateOfTenant();
return this.jdbcTemplate.query(
sqlBuilder.toString(),
this.depositAccountForMaturityRowMapper,
new Object[] { formatter.print(today), DepositAccountType.FIXED_DEPOSIT.getValue(),
DepositAccountType.RECURRING_DEPOSIT.getValue(), SavingsAccountStatusType.ACTIVE.getValue() });
}
@Override
@@ -482,6 +492,27 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
SavingsAccountTransactionType.INTEREST_POSTING.getValue(), SavingsAccountStatusType.ACTIVE.getValue() });
}
@Override
public Collection<Map<String, Object>> retriveDataForRDScheduleCreation() {
final StringBuilder sb = new StringBuilder(300);
sb.append(" select rd.savings_account_id savingsId, rd.mandatory_recommended_deposit_amount as amount,");
sb.append(" mc.recurrence as recurrence ,");
sb.append(" max(ms.duedate) as dueDate , max(ms.installment) as installment,");
sb.append(" count(ms.installment) as futureInstallemts");
sb.append(" from m_deposit_account_term_and_preclosure dat ");
sb.append(" inner join m_savings_account sa on sa.id = dat.savings_account_id and sa.status_enum = ?");
sb.append(" inner join m_deposit_account_recurring_detail rd on rd.savings_account_id = dat.savings_account_id ");
sb.append(" inner join m_calendar_instance mci on mci.entity_type_enum = ? and mci.entity_id = dat.savings_account_id ");
sb.append(" inner join m_calendar mc on mc.id = mci.calendar_id and mc.calendar_type_enum = ?");
sb.append(" inner join m_mandatory_savings_schedule ms on ms.savings_account_id = dat.savings_account_id and ms.duedate > ?");
sb.append(" where dat.deposit_period is null");
sb.append(" group by ms.savings_account_id");
return this.jdbcTemplate.queryForList(sb.toString(), SavingsAccountStatusType.ACTIVE.getValue(),
CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue(),
formatter.print(DateUtils.getLocalDateOfTenant()));
}
private static abstract class DepositAccountMapper implements RowMapper<DepositAccountData> {
private final String selectFieldsSql;
@@ -1300,6 +1331,8 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
sqlBuilder.append("da.account_no as accountNumber, ");
sqlBuilder.append("da.deposit_type_enum as depositTypeId ");
sqlBuilder.append("FROM m_savings_account da ");
sqlBuilder.append("inner join m_deposit_account_term_and_preclosure dat on dat.savings_account_id = da.id ");
sqlBuilder.append("and dat.maturity_date is not null and dat.maturity_date <= ? ");
this.schemaSql = sqlBuilder.toString();
}
@@ -5,6 +5,7 @@
*/
package org.mifosplatform.portfolio.savings.service;
import static org.mifosplatform.portfolio.savings.DepositsApiConstants.RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
@@ -312,8 +313,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
financialYearBeginningMonth);
}
final LocalDate overdueUptoDate = account.maturityDate().isAfter(DateUtils.getLocalDateOfTenant()) ? DateUtils
.getLocalDateOfTenant() : account.maturityDate();
final LocalDate overdueUptoDate = DateUtils.getLocalDateOfTenant();
account.updateOverduePayments(overdueUptoDate);
final boolean isInterestTransfer = false;
if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate())) {
@@ -586,8 +586,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final boolean isPreMatureClosure = false;
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
final LocalDate overdueUptoDate = account.maturityDate().isAfter(DateUtils.getLocalDateOfTenant()) ? DateUtils
.getLocalDateOfTenant() : account.maturityDate();
final LocalDate overdueUptoDate = DateUtils.getLocalDateOfTenant();
if (savingsAccountTransaction.isDeposit()) {
account.updateScheduleInstallments();
@@ -682,8 +681,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
if (savingsAccountTransaction.isDeposit()) {
account.handleScheduleInstallments(savingsAccountTransaction);
}
final LocalDate overdueUptoDate = account.maturityDate().isAfter(DateUtils.getLocalDateOfTenant()) ? DateUtils
.getLocalDateOfTenant() : account.maturityDate();
final LocalDate overdueUptoDate = DateUtils.getLocalDateOfTenant();
account.updateOverduePayments(overdueUptoDate);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
@@ -833,6 +831,13 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(savingsId,
DepositAccountType.RECURRING_DEPOSIT);
checkClientOrGroupActive(account);
if (account.maturityDate() == null) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction);
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("can.not.close.as.premature");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
this.depositAccountDomainService.handleRDAccountPreMatureClosure(account, paymentDetail, user, command,
DateUtils.getLocalDateOfTenant(), changes);
@@ -20,4 +20,6 @@ public interface ScheduledJobRunnerService {
void updateNPA();
void updateMaturityDetailsOfDepositAccounts();
void generateRDSchedule();
}
@@ -5,17 +5,25 @@
*/
package org.mifosplatform.scheduledjobs.service;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.core.service.RoutingDataSourceServiceFactory;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.jobs.annotation.CronTarget;
import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException;
import org.mifosplatform.infrastructure.jobs.service.JobName;
import org.mifosplatform.portfolio.savings.DepositAccountType;
import org.mifosplatform.portfolio.savings.DepositAccountUtils;
import org.mifosplatform.portfolio.savings.data.DepositAccountData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
import org.mifosplatform.portfolio.savings.service.DepositAccountReadPlatformService;
@@ -33,6 +41,8 @@ import org.springframework.transaction.annotation.Transactional;
public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService {
private final static Logger logger = LoggerFactory.getLogger(ScheduledJobRunnerServiceImpl.class);
private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
private final DateTimeFormatter formatterWithTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
private final RoutingDataSourceServiceFactory dataSourceServiceFactory;
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
@@ -285,4 +295,60 @@ public class ScheduledJobRunnerServiceImpl implements ScheduledJobRunnerService
logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Deposit accounts affected by update: " + depositAccounts.size());
}
@Override
@CronTarget(jobName = JobName.GENERATE_RD_SCEHDULE)
public void generateRDSchedule() {
final JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
final Collection<Map<String, Object>> scheduleDetails = this.depositAccountReadPlatformService.retriveDataForRDScheduleCreation();
String insertSql = "INSERT INTO `m_mandatory_savings_schedule` (`savings_account_id`, `duedate`, `installment`, `deposit_amount`, `completed_derived`, `created_date`, `lastmodified_date`) VALUES ";
StringBuilder sb = new StringBuilder();
String currentDate = formatterWithTime.print(DateUtils.getLocalDateTimeOfTenant());
int iterations = 0;
for (Map<String, Object> details : scheduleDetails) {
Long count = (Long) details.get("futureInstallemts");
if (count == null) {
count = 0l;
}
final Long savingsId = (Long) details.get("savingsId");
final BigDecimal amount = (BigDecimal) details.get("amount");
final String recurrence = (String) details.get("recurrence");
Date date = (Date) details.get("dueDate");
LocalDate lastDepositDate = new LocalDate(date);
Integer installmentNumber = (Integer) details.get("installment");
while (count < DepositAccountUtils.GENERATE_MINIMUM_NUMBER_OF_FUTURE_INSTALMENTS) {
count++;
installmentNumber++;
lastDepositDate = DepositAccountUtils.calculateNextDepositDate(lastDepositDate, recurrence);
if (sb.length() > 0) {
sb.append(", ");
}
sb.append("(");
sb.append(savingsId);
sb.append(",'");
sb.append(formatter.print(lastDepositDate));
sb.append("',");
sb.append(installmentNumber);
sb.append(",");
sb.append(amount);
sb.append(", b'0','");
sb.append(currentDate);
sb.append("','");
sb.append(currentDate);
sb.append("')");
iterations++;
if (iterations > 200) {
jdbcTemplate.update(insertSql + sb.toString());
sb = new StringBuilder();
}
}
}
if (sb.length() > 0) {
jdbcTemplate.update(insertSql + sb.toString());
}
}
}
@@ -0,0 +1,2 @@
INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`) VALUES ('Generate Mandatory Savings Schedule', 'Generate Mandatory Savings Schedule', '0 5 0 1/1 * ? *', now());