MIFOSX-1850 : Recurring deposits without maturity date
This commit is contained in:
+2
-1
@@ -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;
|
||||
|
||||
|
||||
+47
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+13
-6
@@ -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)) {
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+5
@@ -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;
|
||||
|
||||
+86
-70
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -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();
|
||||
}
|
||||
|
||||
+36
-3
@@ -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();
|
||||
}
|
||||
|
||||
+11
-6
@@ -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);
|
||||
|
||||
+2
@@ -20,4 +20,6 @@ public interface ScheduledJobRunnerService {
|
||||
void updateNPA();
|
||||
|
||||
void updateMaturityDetailsOfDepositAccounts();
|
||||
|
||||
void generateRDSchedule();
|
||||
}
|
||||
|
||||
+66
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user