From cb48a79e249260b6172ab73c6c75d2c0e23b7ada Mon Sep 17 00:00:00 2001 From: Vishwas Babu A J Date: Sat, 6 Dec 2014 10:23:22 -0800 Subject: [PATCH] formatting savings classes --- .../DepositAccountOnHoldTransaction.java | 2 +- ...sitAccountOnHoldTransactionRepository.java | 5 +- .../savings/domain/FixedDepositAccount.java | 88 +- .../domain/RecurringDepositAccount.java | 2494 +++++++---------- .../domain/SavingsAccountAssembler.java | 2 +- .../domain/SavingsAccountTransaction.java | 16 +- .../savings/domain/SavingsHelper.java | 123 +- .../SavingsOfficerAssignmentHistory.java | 13 +- .../domain/interest/PostingPeriod.java | 8 +- 9 files changed, 1179 insertions(+), 1572 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountOnHoldTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountOnHoldTransaction.java index 99157ca8a..4808f2a01 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountOnHoldTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountOnHoldTransaction.java @@ -50,7 +50,7 @@ public class DepositAccountOnHoldTransaction extends AbstractPersistable { @Temporal(TemporalType.TIMESTAMP) @Column(name = "created_date", nullable = false) private Date createdDate; - + @OneToOne(cascade = CascadeType.ALL, mappedBy = "depositAccountOnHoldTransaction", optional = true, orphanRemoval = true) private GuarantorFundingTransaction guarantorFundingTransaction; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountOnHoldTransactionRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountOnHoldTransactionRepository.java index 89c10cd70..3b0259354 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountOnHoldTransactionRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/DepositAccountOnHoldTransactionRepository.java @@ -8,8 +8,7 @@ package org.mifosplatform.portfolio.savings.domain; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; - -public interface DepositAccountOnHoldTransactionRepository extends JpaRepository, -JpaSpecificationExecutor{ +public interface DepositAccountOnHoldTransactionRepository extends JpaRepository, + JpaSpecificationExecutor { } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositAccount.java index 7c91b5a76..8e8d75e7c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/FixedDepositAccount.java @@ -174,25 +174,33 @@ public class FixedDepositAccount extends SavingsAccount { return applicableInterestRate.divide(BigDecimal.valueOf(100l), mc); } - public void updateMaturityDateAndAmountBeforeAccountActivation(final MathContext mc, - final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { + public void updateMaturityDateAndAmountBeforeAccountActivation(final MathContext mc, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { List allTransactions = new ArrayList<>(); final Money transactionAmountMoney = Money.of(getCurrency(), this.accountTermAndPreClosure.depositAmount()); final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, - this.accountSubmittedOrActivationDate(), transactionAmountMoney, new Date(), null); // TODO: verify if it is ok to pass null for AppUser + this.accountSubmittedOrActivationDate(), transactionAmountMoney, new Date(), null); // TODO: + // verify + // if + // it + // is + // ok + // to + // pass + // null + // for + // AppUser transaction.updateRunningBalance(transactionAmountMoney); transaction.updateCumulativeBalanceAndDates(this.getCurrency(), interestCalculatedUpto()); allTransactions.add(transaction); - updateMaturityDateAndAmount(mc, allTransactions, isPreMatureClosure, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + updateMaturityDateAndAmount(mc, allTransactions, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); } public void updateMaturityDateAndAmount(final MathContext mc, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { updateMaturityDateAndAmount(mc, retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); } public void updateMaturityDateAndAmount(final MathContext mc, final List transactions, @@ -205,9 +213,8 @@ public class FixedDepositAccount extends SavingsAccount { // calculation this.resetAccountTransactionsEndOfDayBalances(transactions, maturityDate); - final List postingPeriods = calculateInterestPayable(mc, interestCalculationUpto, - transactions, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + final List postingPeriods = calculateInterestPayable(mc, interestCalculationUpto, transactions, isPreMatureClosure, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); // reset end of day balance back to today's date this.resetAccountTransactionsEndOfDayBalances(transactions, DateUtils.getLocalDateOfTenant()); @@ -222,8 +229,7 @@ public class FixedDepositAccount extends SavingsAccount { this.accountTermAndPreClosure.updateMaturityDetails(maturityAmount.getAmount(), maturityDate); } - public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { + public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.updateMaturityDetailsAction); @@ -238,8 +244,7 @@ public class FixedDepositAccount extends SavingsAccount { if (!this.maturityDate().isAfter(todayDate)) { // update account status this.status = SavingsAccountStatusType.MATURED.getValue(); - postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); } } @@ -270,8 +275,7 @@ public class FixedDepositAccount extends SavingsAccount { private List calculateInterestPayable(final MathContext mc, final LocalDate maturityDate, final List transactions, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); @@ -282,8 +286,7 @@ public class FixedDepositAccount extends SavingsAccount { .fromInt(this.interestCalculationDaysInYearType); final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods( - accountSubmittedOrActivationDate(), maturityDate, postingPeriodType, - financialYearBeginningMonth); + accountSubmittedOrActivationDate(), maturityDate, postingPeriodType, financialYearBeginningMonth); final List allPostingPeriods = new ArrayList<>(); @@ -470,14 +473,12 @@ public class FixedDepositAccount extends SavingsAccount { // this.savingsAccountTransactionSummaryWrapper, this.transactions); } - public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { + public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { final LocalDate interestPostingUpToDate = maturityDate(); final MathContext mc = MathContext.DECIMAL64; final boolean isInterestTransfer = false; - final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, - isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); Money interestPostedToDate = Money.zero(this.currency); @@ -522,15 +523,13 @@ public class FixedDepositAccount extends SavingsAccount { } public void postPreMaturityInterest(final LocalDate accountCloseDate, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { Money interestPostedToDate = totalInterestPosted(); // calculate interest before one day of closure date final LocalDate interestCalculatedToDate = accountCloseDate.minusDays(1); final Money interestOnMaturity = calculatePreMatureInterest(interestCalculatedToDate, - retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, - isSavingsInterestPostingAtCurrentPeriodEnd, + retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); boolean recalucateDailyBalance = false; @@ -554,14 +553,12 @@ public class FixedDepositAccount extends SavingsAccount { } public BigDecimal calculatePreMatureAmount(final LocalDate preMatureDate, final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { final Money interestPostedToDate = totalInterestPosted().copy(); final Money interestEarnedTillDate = calculatePreMatureInterest(preMatureDate, retreiveOrderedNonInterestPostingTransactions(), - isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); final Money accountBalance = Money.of(getCurrency(), getAccountBalance()); final Money maturityAmount = accountBalance.minus(interestPostedToDate).plus(interestEarnedTillDate); @@ -573,9 +570,8 @@ public class FixedDepositAccount extends SavingsAccount { final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { final MathContext mc = MathContext.DECIMAL64; - final List postingPeriods = calculateInterestPayable(mc, preMatureDate, transactions, - isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + final List postingPeriods = calculateInterestPayable(mc, preMatureDate, transactions, isPreMatureClosure, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); Money interestOnMaturity = Money.zero(this.currency); @@ -589,22 +585,18 @@ public class FixedDepositAccount extends SavingsAccount { @Override public void postInterest(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - } + super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + } @Override - public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, - boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { + public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); } private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java index c7d2133db..33f71ddd3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/RecurringDepositAccount.java @@ -66,1441 +66,1061 @@ import org.mifosplatform.useradministration.domain.AppUser; @DiscriminatorValue("300") public class RecurringDepositAccount extends SavingsAccount { - @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) - private DepositAccountTermAndPreClosure accountTermAndPreClosure; - - @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) - private DepositAccountRecurringDetail recurringDetail; - - @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "account") - private DepositAccountInterestRateChart chart; - - @LazyCollection(LazyCollectionOption.FALSE) - @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true) - private List depositScheduleInstallments = new ArrayList<>(); - - protected RecurringDepositAccount() { - // - } - - public static RecurringDepositAccount createNewApplicationForSubmittal( - final Client client, - final Group group, - final SavingsProduct product, - final Staff fieldOfficer, - final String accountNo, - final String externalId, - final AccountType accountType, - final LocalDate submittedOnDate, - final AppUser submittedBy, - final BigDecimal interestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsPostingInterestPeriodType interestPostingPeriodType, - final SavingsInterestCalculationType interestCalculationType, - final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, - final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, - final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final boolean withdrawalFeeApplicableForTransfer, - final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, - final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart) { - - final boolean allowOverdraft = false; - final BigDecimal overdraftLimit = new BigDecimal(0); - - final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; - return new RecurringDepositAccount(client, group, product, - fieldOfficer, accountNo, externalId, status, accountType, - submittedOnDate, submittedBy, interestRate, - interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, - minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, - savingsAccountCharges, accountTermAndPreClosure, - recurringDetail, chart, allowOverdraft, overdraftLimit); - } - - public static RecurringDepositAccount createNewActivatedAccount( - final Client client, - final Group group, - final SavingsProduct product, - final Staff fieldOfficer, - final String accountNo, - final String externalId, - final AccountType accountType, - final LocalDate submittedOnDate, - final AppUser submittedBy, - final BigDecimal interestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsPostingInterestPeriodType interestPostingPeriodType, - final SavingsInterestCalculationType interestCalculationType, - final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, - final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, - final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final boolean withdrawalFeeApplicableForTransfer, - final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, - final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart) { - - final boolean allowOverdraft = false; - final BigDecimal overdraftLimit = new BigDecimal(0); - - final SavingsAccountStatusType status = SavingsAccountStatusType.ACTIVE; - return new RecurringDepositAccount(client, group, product, - fieldOfficer, accountNo, externalId, status, accountType, - submittedOnDate, submittedBy, interestRate, - interestCompoundingPeriodType, interestPostingPeriodType, - interestCalculationType, interestCalculationDaysInYearType, - minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, - savingsAccountCharges, accountTermAndPreClosure, - recurringDetail, chart, allowOverdraft, overdraftLimit); - } - - private RecurringDepositAccount( - final Client client, - final Group group, - final SavingsProduct product, - final Staff fieldOfficer, - final String accountNo, - final String externalId, - final SavingsAccountStatusType status, - final AccountType accountType, - final LocalDate submittedOnDate, - final AppUser submittedBy, - final BigDecimal nominalAnnualInterestRate, - final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, - final SavingsPostingInterestPeriodType interestPostingPeriodType, - final SavingsInterestCalculationType interestCalculationType, - final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, - final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, - final SavingsPeriodFrequencyType lockinPeriodFrequencyType, - final boolean withdrawalFeeApplicableForTransfer, - final Set savingsAccountCharges, - final DepositAccountTermAndPreClosure accountTermAndPreClosure, - final DepositAccountRecurringDetail recurringDetail, - final DepositAccountInterestRateChart chart, - final boolean allowOverdraft, final BigDecimal overdraftLimit) { - - super(client, group, product, fieldOfficer, accountNo, externalId, - status, accountType, submittedOnDate, submittedBy, - nominalAnnualInterestRate, interestCompoundingPeriodType, - interestPostingPeriodType, interestCalculationType, - interestCalculationDaysInYearType, minRequiredOpeningBalance, - lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeApplicableForTransfer, savingsAccountCharges, - allowOverdraft, overdraftLimit); - - this.accountTermAndPreClosure = accountTermAndPreClosure; - this.recurringDetail = recurringDetail; - this.chart = chart; - if (this.chart != null) { - this.chart.updateDepositAccountReference(this); - } - } - - @Override - public void modifyApplication(final JsonCommand command, - final Map actualChanges) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( - dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME - + SavingsApiConstants.modifyApplicationAction); - super.modifyApplication(command, actualChanges, baseDataValidator); - final Map termAndPreClosureChanges = accountTermAndPreClosure - .update(command, baseDataValidator); - actualChanges.putAll(termAndPreClosureChanges); - recurringDetail.update(command); - - validateDomainRules(baseDataValidator); - super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - @Override - protected BigDecimal getEffectiveInterestRateAsFraction( - final MathContext mc, final LocalDate interestPostingUpToDate) { - boolean isPreMatureClosure = false; - return getEffectiveInterestRateAsFraction(mc, interestPostingUpToDate, - isPreMatureClosure); - } - - protected BigDecimal getEffectiveInterestRateAsFraction( - final MathContext mc, final LocalDate interestPostingUpToDate, - final boolean isPreMatureClosure) { - - boolean applyPreMaturePenalty = false; - BigDecimal penalInterest = BigDecimal.ZERO; - LocalDate depositCloseDate = calculateMaturityDate(); - if (isPreMatureClosure) { - if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) { - applyPreMaturePenalty = true; - penalInterest = this.accountTermAndPreClosure - .depositPreClosureDetail().preClosurePenalInterest(); - final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure - .depositPreClosureDetail() - .preClosurePenalInterestOnType(); - if (preClosurePenalInterestOnType.isWholeTerm()) { - depositCloseDate = interestCalculatedUpto(); - } else if (preClosurePenalInterestOnType - .isTillPrematureWithdrawal()) { - depositCloseDate = interestPostingUpToDate; - } - } - } - - final BigDecimal depositAmount = accountTermAndPreClosure - .depositAmount(); - BigDecimal applicableInterestRate = this.chart - .getApplicableInterestRate(depositAmount, depositStartDate(), - depositCloseDate, this.client); - - if (applyPreMaturePenalty) { - applicableInterestRate = applicableInterestRate - .subtract(penalInterest); - applicableInterestRate = applicableInterestRate - .compareTo(BigDecimal.ZERO) == -1 ? BigDecimal.ZERO - : applicableInterestRate; - } - - this.nominalAnnualInterestRate = applicableInterestRate; - - return applicableInterestRate.divide(BigDecimal.valueOf(100l), mc); - } - - 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 allTransactions = generateIncludingFutureTransactions(interestCalculationUpto); - - final List postingPeriods = calculateInterestPayable(mc, - interestCalculationUpto, allTransactions, isPreMatureClosure, - isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - Money totalInterestPayable = Money.zero(getCurrency()); - Money totalDepositAmount = Money.zero(getCurrency()); - for (PostingPeriod postingPeriod : postingPeriods) { - totalInterestPayable = totalInterestPayable.plus(postingPeriod - .getInterestEarned()); - totalDepositAmount = totalDepositAmount.plus( - postingPeriod.closingBalance()).minus( - postingPeriod.openingBalance()); - } - - this.accountTermAndPreClosure.updateMaturityDetails( - totalDepositAmount.getAmount(), - totalInterestPayable.getAmount(), maturityDate); - } - - public void updateMaturityStatus( - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( - dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME - + SavingsApiConstants.updateMaturityDetailsAction); - - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType - .fromInt(this.status); - if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - "not.in.active.state"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - - final LocalDate todayDate = DateUtils.getLocalDateOfTenant(); - if (!this.maturityDate().isAfter(todayDate)) { - // update account status - this.status = SavingsAccountStatusType.MATURED.getValue(); - postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - } - } - - public LocalDate calculateMaturityDate() { - - final LocalDate startDate = depositStartDate(); - LocalDate maturityDate = null; - final Integer depositPeriod = this.accountTermAndPreClosure - .depositPeriod(); - switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) { - case DAYS: - maturityDate = startDate.plusDays(depositPeriod); - break; - case WEEKS: - maturityDate = startDate.plusWeeks(depositPeriod); - break; - case MONTHS: - maturityDate = startDate.plusMonths(depositPeriod); - break; - case YEARS: - maturityDate = startDate.plusYears(depositPeriod); - break; - case INVALID: - break; - } - - return maturityDate; - } - - private List calculateInterestPayable(final MathContext mc, - final LocalDate maturityDate, - final List transactions, - final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - - // 1. default to calculate interest based on entire history OR - // 2. determine latest 'posting period' and find interest credited to - // that period - - // A generate list of EndOfDayBalances (not including interest postings) - final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType - .fromInt(this.interestPostingPeriodType); - - final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType - .fromInt(this.interestCompoundingPeriodType); - - final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType - .fromInt(this.interestCalculationDaysInYearType); - - final List postingPeriodIntervals = this.savingsHelper - .determineInterestPostingPeriods(depositStartDate(), - maturityDate, postingPeriodType, - financialYearBeginningMonth); - - final List allPostingPeriods = new ArrayList<>(); - - Money periodStartingBalance = Money.zero(currency); - - final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType - .fromInt(this.interestCalculationType); - final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction( - mc, maturityDate, isPreMatureClosure); - final Collection interestPostTransactions = this.savingsHelper - .fetchPostInterestTransactionIds(getId()); - boolean isInterestTransfer = false; - final Money minBalanceForInterestCalculation = Money.of(getCurrency(), - minBalanceForInterestCalculation()); - for (final LocalDateInterval periodInterval : postingPeriodIntervals) { - final PostingPeriod postingPeriod = PostingPeriod.createFrom( - periodInterval, periodStartingBalance, transactions, - this.currency, compoundingPeriodType, - interestCalculationType, interestRateAsFraction, - daysInYearType.getValue(), maturityDate, - interestPostTransactions, isInterestTransfer, - minBalanceForInterestCalculation, - isSavingsInterestPostingAtCurrentPeriodEnd); - - periodStartingBalance = postingPeriod.closingBalance(); - - allPostingPeriods.add(postingPeriod); - } - - this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, - allPostingPeriods, this.getLockedInUntilLocalDate(), - isTransferInterestToOtherAccount()); - // this.summary.updateFromInterestPeriodSummaries(this.currency, - // allPostingPeriods); - return allPostingPeriods; - } - - private List generateIncludingFutureTransactions( - final LocalDate depositEndDate) { - List 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); - } - } - - allTransactions = sortTransactions(allTransactions); - Money runningBalance = Money.zero(getCurrency()); - for (final SavingsAccountTransaction transaction : allTransactions) { - if (transaction.isReversed()) { - transaction.zeroBalanceFields(); - } else { - - Money transactionAmount = Money.zero(this.currency); - if (transaction.isCredit()) { - transactionAmount = transactionAmount.plus(transaction - .getAmount(this.currency)); - } else if (transaction.isDebit()) { - transactionAmount = transactionAmount.minus(transaction - .getAmount(this.currency)); - } - - runningBalance = runningBalance.plus(transactionAmount); - transaction.updateRunningBalance(runningBalance); - } - } - // loop over transactions in reverse - LocalDate endOfBalanceDate = depositEndDate; - for (int i = allTransactions.size() - 1; i >= 0; i--) { - final SavingsAccountTransaction transaction = allTransactions - .get(i); - if (transaction.isNotReversed() - && !transaction.isInterestPostingAndNotReversed()) { - transaction.updateCumulativeBalanceAndDates(this.currency, - endOfBalanceDate); - endOfBalanceDate = transaction.transactionLocalDate() - .minusDays(1); - } - } - 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(); - return depositStartDate; - } - - public void prematureClosure(final AppUser currentUser, - final JsonCommand command, final LocalDate tenantsTodayDate, - final Map actualChanges) { - - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( - dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME - + DepositsApiConstants.preMatureCloseAction); - - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType - .fromInt(this.status); - if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - "not.in.active.state"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - - final Locale locale = command.extractLocale(); - final DateTimeFormatter fmt = DateTimeFormat.forPattern( - command.dateFormat()).withLocale(locale); - final LocalDate closedDate = command - .localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - - if (closedDate.isBefore(getActivationLocalDate())) { - baseDataValidator.reset() - .parameter(SavingsApiConstants.closedOnDateParamName) - .value(closedDate) - .failWithCode("must.be.after.activation.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - - if (isAccountLocked(closedDate)) { - baseDataValidator.reset() - .parameter(SavingsApiConstants.closedOnDateParamName) - .value(closedDate) - .failWithCode("must.be.after.lockin.period"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - - if (closedDate.isAfter(maturityDate())) { - baseDataValidator.reset() - .parameter(SavingsApiConstants.closedOnDateParamName) - .value(closedDate) - .failWithCode("must.be.before.maturity.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - - if (closedDate.isAfter(tenantsTodayDate)) { - baseDataValidator.reset() - .parameter(SavingsApiConstants.closedOnDateParamName) - .value(closedDate).failWithCode("cannot.be.a.future.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - - if (isAccountLocked(calculateMaturityDate())) { - baseDataValidator - .reset() - .failWithCodeNoParameterAddedToErrorCode( - "deposit.period.must.be.greater.than.lock.in.period", - "Deposit period must be greater than account lock-in period."); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - - final List savingsAccountTransactions = retreiveListOfTransactions(); - if (savingsAccountTransactions.size() > 0) { - final SavingsAccountTransaction accountTransaction = savingsAccountTransactions - .get(savingsAccountTransactions.size() - 1); - if (accountTransaction.isAfter(closedDate)) { - baseDataValidator.reset() - .parameter(SavingsApiConstants.closedOnDateParamName) - .value(closedDate) - .failWithCode("must.be.after.last.transaction.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - } - - validateActivityNotBeforeClientOrGroupTransferDate( - SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); - this.status = SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue(); - - final Integer onAccountClosureId = command - .integerValueOfParameterNamed(onAccountClosureIdParamName); - final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType - .fromInt(onAccountClosureId); - this.accountTermAndPreClosure - .updateOnAccountClosureStatus(onClosureType); - - /* - * // withdraw deposit amount before closing the account final Money - * transactionAmountMoney = Money.of(this.currency, - * this.getAccountBalance()); final SavingsAccountTransaction withdraw = - * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, - * closedDate, transactionAmountMoney, new Date()); - * this.transactions.add(withdraw); - */ - actualChanges.put(SavingsApiConstants.statusParamName, - SavingsEnumerations.status(this.status)); - actualChanges - .put(SavingsApiConstants.localeParamName, command.locale()); - actualChanges.put(SavingsApiConstants.dateFormatParamName, - command.dateFormat()); - actualChanges.put(SavingsApiConstants.closedOnDateParamName, - closedDate.toString(fmt)); - - this.rejectedOnDate = null; - this.rejectedBy = null; - this.withdrawnOnDate = null; - this.withdrawnBy = null; - this.closedOnDate = closedDate.toDate(); - this.closedBy = currentUser; - this.summary - .updateSummary(this.currency, - this.savingsAccountTransactionSummaryWrapper, - this.transactions); - - } - - @Override - public Money activateWithBalance() { - return Money.of(this.currency, this.minRequiredOpeningBalance); - } - - protected void processAccountUponActivation(final DateTimeFormatter fmt, - final AppUser user) { - final Money minRequiredOpeningBalance = Money.of(this.currency, - this.minRequiredOpeningBalance); - if (minRequiredOpeningBalance.isGreaterThanZero()) { - final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO( - fmt, getActivationLocalDate(), - minRequiredOpeningBalance.getAmount(), null, new Date(), - user); - deposit(transactionDTO); - - // update existing transactions so derived balance fields are - // correct. - recalculateDailyBalances(Money.zero(this.currency), - DateUtils.getLocalDateOfTenant()); - } - } - - public void close(final AppUser currentUser, final JsonCommand command, - final LocalDate tenantsTodayDate, - final Map actualChanges) { - - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( - dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME - + SavingsApiConstants.closeAction); - - final SavingsAccountStatusType currentStatus = SavingsAccountStatusType - .fromInt(this.status); - if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus)) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - "not.in.matured.state"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - - final Locale locale = command.extractLocale(); - final DateTimeFormatter fmt = DateTimeFormat.forPattern( - command.dateFormat()).withLocale(locale); - final LocalDate closedDate = command - .localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - - if (closedDate.isBefore(getActivationLocalDate())) { - baseDataValidator.reset() - .parameter(SavingsApiConstants.closedOnDateParamName) - .value(closedDate) - .failWithCode("must.be.after.activation.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - if (closedDate.isBefore(maturityDate())) { - baseDataValidator.reset() - .parameter(SavingsApiConstants.closedOnDateParamName) - .value(closedDate) - .failWithCode("must.be.after.account.maturity.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - if (closedDate.isAfter(tenantsTodayDate)) { - baseDataValidator.reset() - .parameter(SavingsApiConstants.closedOnDateParamName) - .value(closedDate).failWithCode("cannot.be.a.future.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - final List savingsAccountTransactions = retreiveListOfTransactions(); - if (savingsAccountTransactions.size() > 0) { - final SavingsAccountTransaction accountTransaction = savingsAccountTransactions - .get(savingsAccountTransactions.size() - 1); - if (accountTransaction.isAfter(closedDate)) { - baseDataValidator.reset() - .parameter(SavingsApiConstants.closedOnDateParamName) - .value(closedDate) - .failWithCode("must.be.after.last.transaction.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - } - - validateActivityNotBeforeClientOrGroupTransferDate( - SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); - this.status = SavingsAccountStatusType.CLOSED.getValue(); - - final Integer onAccountClosureId = command - .integerValueOfParameterNamed(onAccountClosureIdParamName); - final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType - .fromInt(onAccountClosureId); - this.accountTermAndPreClosure - .updateOnAccountClosureStatus(onClosureType); - - /* - * // withdraw deposit amount before closing the account final Money - * transactionAmountMoney = Money.of(this.currency, - * this.getAccountBalance()); final SavingsAccountTransaction withdraw = - * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, - * closedDate, transactionAmountMoney, new Date()); - * this.transactions.add(withdraw); - */ - - actualChanges.put(SavingsApiConstants.statusParamName, - SavingsEnumerations.status(this.status)); - actualChanges - .put(SavingsApiConstants.localeParamName, command.locale()); - actualChanges.put(SavingsApiConstants.dateFormatParamName, - command.dateFormat()); - actualChanges.put(SavingsApiConstants.closedOnDateParamName, - closedDate.toString(fmt)); - - this.rejectedOnDate = null; - this.rejectedBy = null; - this.withdrawnOnDate = null; - this.withdrawnBy = null; - this.closedOnDate = closedDate.toDate(); - this.closedBy = currentUser; - this.summary - .updateSummary(this.currency, - this.savingsAccountTransactionSummaryWrapper, - this.transactions); - } - - public void postMaturityInterest( - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final LocalDate interestPostingUpToDate = maturityDate(); - final MathContext mc = MathContext.DECIMAL64; - boolean isInterestTransfer = false; - final List postingPeriods = calculateInterestUsing(mc, - interestPostingUpToDate, isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - - Money interestPostedToDate = Money.zero(this.currency); - - boolean recalucateDailyBalanceDetails = false; - - for (final PostingPeriod interestPostingPeriod : postingPeriods) { - - LocalDate interestPostingTransactionDate = interestPostingPeriod - .dateOfPostingTransaction(); - interestPostingTransactionDate = interestPostingTransactionDate - .isAfter(interestPostingUpToDate) ? interestPostingUpToDate - : interestPostingTransactionDate; - final Money interestEarnedToBePostedForPeriod = interestPostingPeriod - .getInterestEarned(); - - interestPostedToDate = interestPostedToDate - .plus(interestEarnedToBePostedForPeriod); - - final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); - if (postingTransaction == null) { - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction - .interestPosting(this, office(), - interestPostingTransactionDate, - interestEarnedToBePostedForPeriod); - this.transactions.add(newPostingTransaction); - recalucateDailyBalanceDetails = true; - } else { - final boolean correctionRequired = postingTransaction - .hasNotAmount(interestEarnedToBePostedForPeriod); - if (correctionRequired) { - postingTransaction.reverse(); - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction - .interestPosting(this, office(), - interestPostingTransactionDate, - interestEarnedToBePostedForPeriod); - this.transactions.add(newPostingTransaction); - recalucateDailyBalanceDetails = true; - } - } - } - - if (recalucateDailyBalanceDetails) { - // update existing transactions so derived balance fields are - // correct. - recalculateDailyBalances(Money.zero(this.currency), - interestPostingUpToDate); - } - - this.summary - .updateSummary(this.currency, - this.savingsAccountTransactionSummaryWrapper, - this.transactions); - } - - public void postPreMaturityInterest(final LocalDate accountCloseDate, - final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - - final Money interestPostedToDate = totalInterestPosted(); - // calculate interest before one day of closure date - final LocalDate interestCalculatedToDate = accountCloseDate - .minusDays(1); - final Money interestOnMaturity = calculatePreMatureInterest( - interestCalculatedToDate, - retreiveOrderedNonInterestPostingTransactions(), - isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - - boolean recalucateDailyBalance = false; - - // post remaining interest - final Money remainigInterestToBePosted = interestOnMaturity - .minus(interestPostedToDate); - if (!remainigInterestToBePosted.isZero()) { - final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction - .interestPosting(this, office(), accountCloseDate, - remainigInterestToBePosted); - this.transactions.add(newPostingTransaction); - recalucateDailyBalance = true; - } - - if (recalucateDailyBalance) { - // update existing transactions so derived balance fields are - // correct. - recalculateDailyBalances(Money.zero(this.currency), - accountCloseDate); - } - - this.summary - .updateSummary(this.currency, - this.savingsAccountTransactionSummaryWrapper, - this.transactions); - this.accountTermAndPreClosure.updateMaturityDetails( - this.getAccountBalance(), accountCloseDate); - } - - public BigDecimal calculatePreMatureAmount(final LocalDate preMatureDate, - final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - - final Money interestPostedToDate = totalInterestPosted().copy(); - - final Money interestEarnedTillDate = calculatePreMatureInterest( - preMatureDate, retreiveOrderedNonInterestPostingTransactions(), - isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - - final Money accountBalance = Money.of(getCurrency(), - getAccountBalance()); - final Money maturityAmount = accountBalance.minus(interestPostedToDate) - .plus(interestEarnedTillDate); - - return maturityAmount.getAmount(); - } - - private Money calculatePreMatureInterest(final LocalDate preMatureDate, - final List transactions, - final boolean isPreMatureClosure, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final MathContext mc = MathContext.DECIMAL64; - final List postingPeriods = calculateInterestPayable(mc, - preMatureDate, transactions, isPreMatureClosure, - isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - - Money interestOnMaturity = Money.zero(this.currency); - - for (final PostingPeriod interestPostingPeriod : postingPeriods) { - final Money interestEarnedForPeriod = interestPostingPeriod - .getInterestEarned(); - interestOnMaturity = interestOnMaturity - .plus(interestEarnedForPeriod); - } - this.summary.updateFromInterestPeriodSummaries(this.currency, - postingPeriods); - return interestOnMaturity; - } - - @Override - public void postInterest(final MathContext mc, final LocalDate postingDate, - final boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - } - - @Override - public List calculateInterestUsing(final MathContext mc, - final LocalDate postingDate, boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final Integer financialYearBeginningMonth) { - final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); - return super.calculateInterestUsing(mc, interestPostingUpToDate, - isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); - } - - private LocalDate interestPostingUpToDate( - final LocalDate interestPostingDate) { - LocalDate interestPostingUpToDate = interestPostingDate; - final LocalDate uptoMaturityDate = interestCalculatedUpto(); - if (uptoMaturityDate != null - && uptoMaturityDate.isBefore(interestPostingDate)) { - interestPostingUpToDate = uptoMaturityDate; - } - return interestPostingUpToDate; - } - - public LocalDate maturityDate() { - return this.accountTermAndPreClosure.getMaturityLocalDate(); - } - - public BigDecimal maturityAmount() { - return this.accountTermAndPreClosure.maturityAmount(); - } - - private LocalDate interestCalculatedUpto() { - LocalDate uptoMaturityDate = calculateMaturityDate(); - if (uptoMaturityDate != null) { - // interest should not be calculated for maturity day - uptoMaturityDate = uptoMaturityDate.minusDays(1); - } - return uptoMaturityDate; - } - - private Money totalInterestPosted() { - Money interestPostedToDate = Money.zero(this.currency); - for (final SavingsAccountTransaction transaction : this.transactions) { - if (transaction.isInterestPostingAndNotReversed()) { - interestPostedToDate = interestPostedToDate.plus(transaction - .getAmount(currency)); - } - } - - return interestPostedToDate; - } - - @Override - public Map activate(final AppUser currentUser, - final JsonCommand command, final LocalDate tenantsTodayDate) { - - final Map actualChanges = super.activate(currentUser, - command, tenantsTodayDate); - - if (accountTermAndPreClosure - .isAfterExpectedFirstDepositDate(getActivationLocalDate())) { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( - dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - final DateTimeFormatter formatter = DateTimeFormat.forPattern( - command.dateFormat()).withLocale(command.extractLocale()); - final String dateAsString = formatter - .print(this.accountTermAndPreClosure - .getExpectedFirstDepositOnDate()); - baseDataValidator - .reset() - .parameter(DepositsApiConstants.activatedOnDateParamName) - .value(dateAsString) - .failWithCodeNoParameterAddedToErrorCode( - "cannot.be.before.expected.first.deposit.date"); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException( - dataValidationErrors); - } - } - - return actualChanges; - } - - protected List sortTransactions( - final List transactions) { - final List listOfTransactionsSorted = new ArrayList<>(); - listOfTransactionsSorted.addAll(transactions); - - final SavingsAccountTransactionComparator transactionComparator = new SavingsAccountTransactionComparator(); - Collections.sort(listOfTransactionsSorted, transactionComparator); - return listOfTransactionsSorted; - } - - @Override - public SavingsAccountTransaction deposit( - final SavingsAccountTransactionDTO transactionDTO) { - - if (isAccountMatured()) { - final String defaultUserMessage = "Transaction is not allowed. Account is matured."; - final ApiParameterError error = ApiParameterError - .parameterError( - "error.msg.recurring.deposit.account.transaction.account.is.matured", - defaultUserMessage, - "transactionDate", - transactionDTO.getTransactionDate().toString( - transactionDTO.getFormatter())); - - final List dataValidationErrors = new ArrayList<>(); - dataValidationErrors.add(error); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - if (!isBeforeMaturityDate(transactionDTO.getTransactionDate())) { - final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account maturity date."; - final ApiParameterError error = ApiParameterError - .parameterError( - "error.msg.recurring.deposit.account.transaction.date.is.after.account.maturity.date", - defaultUserMessage, - "transactionDate", - transactionDTO.getTransactionDate().toString( - transactionDTO.getFormatter())); - - final List dataValidationErrors = new ArrayList<>(); - dataValidationErrors.add(error); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - if (isBeforeDepositStartDate(transactionDTO.getTransactionDate())) { - final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account activation and deposit start date."; - final ApiParameterError error = ApiParameterError - .parameterError( - "error.msg.recurring.deposit.account.transaction.date.is.before.account.activation.or.deposit.date", - defaultUserMessage, - "transactionDate", - transactionDTO.getTransactionDate().toString( - transactionDTO.getFormatter())); - - final List dataValidationErrors = new ArrayList<>(); - dataValidationErrors.add(error); - - throw new PlatformApiDataValidationException(dataValidationErrors); - } - - final SavingsAccountTransaction transaction = super - .deposit(transactionDTO); - - return transaction; - } - - public void handleScheduleInstallments( - final SavingsAccountTransaction transaction) { - - final LocalDate transactionDate = transaction.transactionLocalDate(); - Money transactionAmountUnprocessed = transaction - .getAmount(getCurrency()); - - for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { - if (currentInstallment.isNotFullyPaidOff() - && transactionAmountUnprocessed.isGreaterThanZero()) { - if (!this.adjustAdvanceTowardsFuturePayments() - && currentInstallment.dueDate() - .isAfter(transactionDate)) { - transactionAmountUnprocessed = Money.zero(getCurrency()); - } - transactionAmountUnprocessed = handleInstallmentTransaction( - currentInstallment, transactionAmountUnprocessed, - transactionDate); - } - } - - } - - public void updateScheduleInstallments() { - - // reset all installments to process from the beginning - for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { - currentInstallment.resetDerivedFields(); - } - - final List orderedDepositTransactions = retreiveOrderedDepositTransactions(); - for (SavingsAccountTransaction transaction : orderedDepositTransactions) { - handleScheduleInstallments(transaction); - } - } - - private List retreiveOrderedDepositTransactions() { - final List listOfTransactionsSorted = retreiveListOfTransactions(); - - final List orderedDepositTransactions = new ArrayList<>(); - - for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) { - if (transaction.isDepositAndNotReversed()) { - orderedDepositTransactions.add(transaction); - } - } - - return orderedDepositTransactions; - } - - /** - * This method is responsible for checking if the current transaction is 'an - * advance/early payment' based on the details passed through. - * - * Default implementation is check transaction date is before installment - * due date. - */ - protected boolean isTransactionInAdvanceOfInstallment( - final int currentInstallmentIndex, - final List installments, - final LocalDate transactionDate) { - - final RecurringDepositScheduleInstallment currentInstallment = installments - .get(currentInstallmentIndex); - - return transactionDate.isBefore(currentInstallment.dueDate()); - } - - private Money handleInstallmentTransaction( - final RecurringDepositScheduleInstallment currentInstallment, - final Money transactionAmountUnprocessed, - final LocalDate transactionDate) { - - Money transactionAmountRemaining = transactionAmountUnprocessed; - Money depositAmountPortion = Money.zero(transactionAmountRemaining - .getCurrency()); - - depositAmountPortion = currentInstallment.payInstallment( - transactionDate, transactionAmountRemaining); - transactionAmountRemaining = transactionAmountRemaining - .minus(depositAmountPortion); - - return transactionAmountRemaining; - - } - - private boolean isAccountMatured() { - return SavingsAccountStatusType.fromInt(status).isMatured(); - } - - private boolean isBeforeMaturityDate(final LocalDate compareDate) { - final LocalDate maturityDate = this.maturityDate(); - return compareDate.isBefore(maturityDate); - } - - private boolean isBeforeDepositStartDate(LocalDate compareDate) { - return compareDate.isBefore(depositStartDate()); - } - - public void validateDomainRules() { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( - dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - validateDomainRules(baseDataValidator); - super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException(dataValidationErrors); - } - } - - private void validateDomainRules( - final DataValidatorBuilder baseDataValidator) { - - final boolean isMinTermGreaterThanMax = this.accountTermAndPreClosure - .depositTermDetail() - .isMinDepositTermGreaterThanMaxDepositTerm(); - // deposit period should be within min and max deposit term - if (isMinTermGreaterThanMax) { - final Integer maxTerm = this.accountTermAndPreClosure - .depositTermDetail().maxDepositTerm(); - baseDataValidator - .reset() - .parameter(maxDepositTermParamName) - .value(maxTerm) - .failWithCodeNoParameterAddedToErrorCode( - "max.term.lessthan.min.term"); - } - - 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 (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()) { - baseDataValidator - .reset() - .failWithCodeNoParameterAddedToErrorCode( - "expected.first.deposit.date.must.be.after.account.submitted.or.activation.date"); - } - - // FIXME: Handle this scenario - /* - * //final boolean recurringFrequencyBeforeDepositPeriod = - * recurringFrequencyBeforeDepositPeriod(); - * - * if (!recurringFrequencyBeforeDepositPeriod) { - * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - * "recurring.frequency.not.before.deposit.period"); } - */ - } - - public void validateApplicableInterestRate() { - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( - dataValidationErrors) - .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); - - if (this.chart != null) { - final LocalDate chartFromDate = this.chart.getFromDateAsLocalDate(); - LocalDate chartEndDate = this.chart.getEndDateAsLocalDate(); - chartEndDate = chartEndDate == null ? DateUtils - .getLocalDateOfTenant() : chartEndDate; - - final LocalDateInterval chartInterval = LocalDateInterval.create( - chartFromDate, chartEndDate); - if (!chartInterval.contains(accountSubmittedOrActivationDate())) { - baseDataValidator - .reset() - .failWithCodeNoParameterAddedToErrorCode( - "no.valid.interest.rate.slab.available.for.date.range"); - } - - final BigDecimal maturityAmount = this.accountTermAndPreClosure - .maturityAmount(); - BigDecimal applicableInterestRate = this.chart - .getApplicableInterestRate(maturityAmount, - depositStartDate(), calculateMaturityDate(), - this.client); - - if (applicableInterestRate.equals(BigDecimal.ZERO)) { - baseDataValidator - .reset() - .failWithCodeNoParameterAddedToErrorCode( - "no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period"); - } - - } else if (this.nominalAnnualInterestRate == null - || this.nominalAnnualInterestRate.compareTo(BigDecimal.ZERO) == 0) { - baseDataValidator - .reset() - .parameter( - DepositsApiConstants.nominalAnnualInterestRateParamName) - .value(nominalAnnualInterestRate) - .failWithCodeNoParameterAddedToErrorCode( - "interest.chart.or.nominal.interest.rate.required"); - } - - /** - * final boolean recurringFrequencyBeforeDepositPeriod = - * recurringFrequencyBeforeDepositPeriod(); - * - * if (!recurringFrequencyBeforeDepositPeriod) { - * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( - * "recurring.frequency.not.before.deposit.period"); } - **/ - } - - public boolean isReinvestOnClosure() { - return this.accountTermAndPreClosure.isReinvestOnClosure(); - } - - public boolean isTransferToSavingsOnClosure() { - return this.accountTermAndPreClosure.isTransferToSavingsOnClosure(); - } - - public RecurringDepositAccount reInvest(BigDecimal depositAmount) { - - final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure - .copy(depositAmount); - final DepositAccountRecurringDetail recurringDetail = this.recurringDetail - .copy(); - final SavingsProduct product = this.product; - final InterestRateChart productChart = product - .applicableChart(getClosedOnDate()); - final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart - .from(productChart); - final String accountNumber = null; - final String externalId = this.externalId; - final AccountType accountType = AccountType.fromInt(this.accountType); - final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType - .fromInt(this.interestPostingPeriodType); - final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType - .fromInt(this.interestCompoundingPeriodType); - final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType - .fromInt(this.interestCalculationType); - final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType - .fromInt(this.interestCalculationDaysInYearType); - final BigDecimal minRequiredOpeningBalance = depositAmount; - final BigDecimal interestRate = BigDecimal.ZERO; - final Set savingsAccountCharges = null; - final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType - .fromInt(this.lockinPeriodFrequencyType); - final Integer lockinPeriodFrequency = this.lockinPeriodFrequency; - final boolean withdrawalFeeApplicableForTransfer = false; - - LocalDate now = DateUtils.getLocalDateOfTenant(); - - newAccountTermAndPreClosure.updateExpectedFirstDepositDate(now); - - RecurringDepositAccount rdAccount = RecurringDepositAccount - .createNewActivatedAccount(client, group, product, - savingsOfficer, accountNumber, externalId, accountType, - getClosedOnDate(), closedBy, interestRate, - compoundingPeriodType, postingPeriodType, - interestCalculationType, daysInYearType, - minRequiredOpeningBalance, lockinPeriodFrequency, - lockinPeriodFrequencyType, - withdrawalFeeApplicableForTransfer, - savingsAccountCharges, newAccountTermAndPreClosure, - recurringDetail, newChart); - - rdAccount.setDatesFrom(now); - newAccountTermAndPreClosure.updateAccountReference(rdAccount); - recurringDetail.updateAccountReference(rdAccount); - - return rdAccount; - - } - - private boolean firstDepositDateBeforeAccountSubmittedOrActivationDate() { - final LocalDate expectedFirstDepositLocalDate = accountTermAndPreClosure - .getExpectedFirstDepositOnDate(); - if (expectedFirstDepositLocalDate == null) - return false; - return expectedFirstDepositLocalDate - .isBefore(accountSubmittedOrActivationDate()); - } - - public void setDatesFrom(final LocalDate now) { - this.rejectedOnDate = null; - this.rejectedBy = null; - this.withdrawnOnDate = null; - this.withdrawnBy = null; - this.closedOnDate = null; - this.closedBy = null; - - this.activatedBy = null; - this.lockedInUntilDate = null; - - this.activatedOnDate = now.toDate(); - } - - @Override - protected boolean isTransferInterestToOtherAccount() { - return this.accountTermAndPreClosure - .isTransferInterestToLinkedAccount(); - } - - public void generateSchedule(final PeriodFrequencyType frequency, - final Integer recurringEvery, final Calendar calendar) { - final List depositScheduleInstallments = depositScheduleInstallments(); - depositScheduleInstallments.clear(); - LocalDate installmentDate = null; - if (this.isCalendarInherited()) { - installmentDate = CalendarUtils.getNextScheduleDate(calendar, - accountSubmittedOrActivationDate()); - } else { - installmentDate = depositStartDate(); - } - - final LocalDate maturityDate = calculateMaturityDate(); - int installmentNumber = 1; - 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); - installmentNumber += 1; - } - } - - private List depositScheduleInstallments() { - if (this.depositScheduleInstallments == null) { - this.depositScheduleInstallments = new ArrayList<>(); - } - return this.depositScheduleInstallments; - } - - public boolean isCalendarInherited() { - return this.recurringDetail.isCalendarInherited(); - } - - public void updateOverduePayments(final LocalDate todayDate) { - LocalDate overdueUptoDate = this.maturityDate(); - if (overdueUptoDate.isAfter(todayDate)) { - overdueUptoDate = todayDate; - } - - final List installments = depositScheduleInstallments(); - int noOfOverdueInstallments = 0; - Money totalOverdueAmount = Money.zero(getCurrency()); - for (RecurringDepositScheduleInstallment installment : installments) { - if (installment.isNotFullyPaidOff() - && overdueUptoDate.isAfter(installment.dueDate())) { - noOfOverdueInstallments++; - totalOverdueAmount = totalOverdueAmount.plus(installment - .getDepositAmountOutstanding(getCurrency())); - } - } - this.recurringDetail.updateOverdueDetails(noOfOverdueInstallments, - totalOverdueAmount); - } - - @Override - public boolean allowWithdrawal() { - return this.recurringDetail.allowWithdrawal(); - } - - public boolean adjustAdvanceTowardsFuturePayments() { - return this.recurringDetail.adjustAdvanceTowardsFuturePayments(); - } - - @Override - public boolean isTransactionsAllowed() { - return isActive() || isAccountMatured(); - } + @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) + private DepositAccountTermAndPreClosure accountTermAndPreClosure; + + @OneToOne(mappedBy = "account", cascade = CascadeType.ALL) + private DepositAccountRecurringDetail recurringDetail; + + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "account") + private DepositAccountInterestRateChart chart; + + @LazyCollection(LazyCollectionOption.FALSE) + @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true) + private List depositScheduleInstallments = new ArrayList<>(); + + protected RecurringDepositAccount() { + // + } + + public static RecurringDepositAccount createNewApplicationForSubmittal(final Client client, final Group group, + final SavingsProduct product, final Staff fieldOfficer, final String accountNo, final String externalId, + final AccountType accountType, final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, + final DepositAccountInterestRateChart chart) { + + final boolean allowOverdraft = false; + final BigDecimal overdraftLimit = new BigDecimal(0); + + final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; + return new RecurringDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, + submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, + recurringDetail, chart, allowOverdraft, overdraftLimit); + } + + public static RecurringDepositAccount createNewActivatedAccount(final Client client, final Group group, final SavingsProduct product, + final Staff fieldOfficer, final String accountNo, final String externalId, final AccountType accountType, + final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal interestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, + final DepositAccountInterestRateChart chart) { + + final boolean allowOverdraft = false; + final BigDecimal overdraftLimit = new BigDecimal(0); + + final SavingsAccountStatusType status = SavingsAccountStatusType.ACTIVE; + return new RecurringDepositAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, + submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, + interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, accountTermAndPreClosure, + recurringDetail, chart, allowOverdraft, overdraftLimit); + } + + private RecurringDepositAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, + final String accountNo, final String externalId, final SavingsAccountStatusType status, final AccountType accountType, + final LocalDate submittedOnDate, final AppUser submittedBy, final BigDecimal nominalAnnualInterestRate, + final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final SavingsInterestCalculationType interestCalculationType, + final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, + final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, + final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, + final DepositAccountTermAndPreClosure accountTermAndPreClosure, final DepositAccountRecurringDetail recurringDetail, + final DepositAccountInterestRateChart chart, final boolean allowOverdraft, final BigDecimal overdraftLimit) { + + super(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, + nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, + withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit); + + this.accountTermAndPreClosure = accountTermAndPreClosure; + this.recurringDetail = recurringDetail; + this.chart = chart; + if (this.chart != null) { + this.chart.updateDepositAccountReference(this); + } + } + + @Override + public void modifyApplication(final JsonCommand command, final Map actualChanges) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.modifyApplicationAction); + super.modifyApplication(command, actualChanges, baseDataValidator); + final Map termAndPreClosureChanges = accountTermAndPreClosure.update(command, baseDataValidator); + actualChanges.putAll(termAndPreClosureChanges); + recurringDetail.update(command); + + validateDomainRules(baseDataValidator); + super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + @Override + protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate) { + boolean isPreMatureClosure = false; + return getEffectiveInterestRateAsFraction(mc, interestPostingUpToDate, isPreMatureClosure); + } + + protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate interestPostingUpToDate, + final boolean isPreMatureClosure) { + + boolean applyPreMaturePenalty = false; + BigDecimal penalInterest = BigDecimal.ZERO; + LocalDate depositCloseDate = calculateMaturityDate(); + if (isPreMatureClosure) { + if (this.accountTermAndPreClosure.isPreClosurePenalApplicable()) { + applyPreMaturePenalty = true; + penalInterest = this.accountTermAndPreClosure.depositPreClosureDetail().preClosurePenalInterest(); + final PreClosurePenalInterestOnType preClosurePenalInterestOnType = this.accountTermAndPreClosure.depositPreClosureDetail() + .preClosurePenalInterestOnType(); + if (preClosurePenalInterestOnType.isWholeTerm()) { + depositCloseDate = interestCalculatedUpto(); + } else if (preClosurePenalInterestOnType.isTillPrematureWithdrawal()) { + depositCloseDate = interestPostingUpToDate; + } + } + } + + final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount(); + BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate, + this.client); + + if (applyPreMaturePenalty) { + applicableInterestRate = applicableInterestRate.subtract(penalInterest); + applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) == -1 ? BigDecimal.ZERO : applicableInterestRate; + } + + this.nominalAnnualInterestRate = applicableInterestRate; + + return applicableInterestRate.divide(BigDecimal.valueOf(100l), mc); + } + + 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 allTransactions = generateIncludingFutureTransactions(interestCalculationUpto); + + final List postingPeriods = calculateInterestPayable(mc, interestCalculationUpto, allTransactions, + isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + Money totalInterestPayable = Money.zero(getCurrency()); + Money totalDepositAmount = Money.zero(getCurrency()); + for (PostingPeriod postingPeriod : postingPeriods) { + totalInterestPayable = totalInterestPayable.plus(postingPeriod.getInterestEarned()); + totalDepositAmount = totalDepositAmount.plus(postingPeriod.closingBalance()).minus(postingPeriod.openingBalance()); + } + + this.accountTermAndPreClosure.updateMaturityDetails(totalDepositAmount.getAmount(), totalInterestPayable.getAmount(), maturityDate); + } + + public void updateMaturityStatus(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.updateMaturityDetailsAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + final LocalDate todayDate = DateUtils.getLocalDateOfTenant(); + if (!this.maturityDate().isAfter(todayDate)) { + // update account status + this.status = SavingsAccountStatusType.MATURED.getValue(); + postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + } + } + + public LocalDate calculateMaturityDate() { + + final LocalDate startDate = depositStartDate(); + LocalDate maturityDate = null; + final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod(); + switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) { + case DAYS: + maturityDate = startDate.plusDays(depositPeriod); + break; + case WEEKS: + maturityDate = startDate.plusWeeks(depositPeriod); + break; + case MONTHS: + maturityDate = startDate.plusMonths(depositPeriod); + break; + case YEARS: + maturityDate = startDate.plusYears(depositPeriod); + break; + case INVALID: + break; + } + + return maturityDate; + } + + private List calculateInterestPayable(final MathContext mc, final LocalDate maturityDate, + final List transactions, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + + // 1. default to calculate interest based on entire history OR + // 2. determine latest 'posting period' and find interest credited to + // that period + + // A generate list of EndOfDayBalances (not including interest postings) + final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); + + final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType + .fromInt(this.interestCompoundingPeriodType); + + final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType + .fromInt(this.interestCalculationDaysInYearType); + + final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(depositStartDate(), + maturityDate, postingPeriodType, financialYearBeginningMonth); + + final List allPostingPeriods = new ArrayList<>(); + + Money periodStartingBalance = Money.zero(currency); + + final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); + final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, maturityDate, isPreMatureClosure); + final Collection interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(getId()); + boolean isInterestTransfer = false; + final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation()); + for (final LocalDateInterval periodInterval : postingPeriodIntervals) { + final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, transactions, + this.currency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYearType.getValue(), + maturityDate, interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation, + isSavingsInterestPostingAtCurrentPeriodEnd); + + periodStartingBalance = postingPeriod.closingBalance(); + + allPostingPeriods.add(postingPeriod); + } + + this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, this.getLockedInUntilLocalDate(), + isTransferInterestToOtherAccount()); + // this.summary.updateFromInterestPeriodSummaries(this.currency, + // allPostingPeriods); + return allPostingPeriods; + } + + private List generateIncludingFutureTransactions(final LocalDate depositEndDate) { + List 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); + } + } + + allTransactions = sortTransactions(allTransactions); + Money runningBalance = Money.zero(getCurrency()); + for (final SavingsAccountTransaction transaction : allTransactions) { + if (transaction.isReversed()) { + transaction.zeroBalanceFields(); + } else { + + Money transactionAmount = Money.zero(this.currency); + if (transaction.isCredit()) { + transactionAmount = transactionAmount.plus(transaction.getAmount(this.currency)); + } else if (transaction.isDebit()) { + transactionAmount = transactionAmount.minus(transaction.getAmount(this.currency)); + } + + runningBalance = runningBalance.plus(transactionAmount); + transaction.updateRunningBalance(runningBalance); + } + } + // loop over transactions in reverse + LocalDate endOfBalanceDate = depositEndDate; + for (int i = allTransactions.size() - 1; i >= 0; i--) { + final SavingsAccountTransaction transaction = allTransactions.get(i); + if (transaction.isNotReversed() && !transaction.isInterestPostingAndNotReversed()) { + transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate); + endOfBalanceDate = transaction.transactionLocalDate().minusDays(1); + } + } + 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(); + return depositStartDate; + } + + public void prematureClosure(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate, + final Map actualChanges) { + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.active.state"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); + + if (closedDate.isBefore(getActivationLocalDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.activation.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (isAccountLocked(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.lockin.period"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (closedDate.isAfter(maturityDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.before.maturity.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (closedDate.isAfter(tenantsTodayDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("cannot.be.a.future.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + if (isAccountLocked(calculateMaturityDate())) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("deposit.period.must.be.greater.than.lock.in.period", + "Deposit period must be greater than account lock-in period."); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + final List savingsAccountTransactions = retreiveListOfTransactions(); + if (savingsAccountTransactions.size() > 0) { + final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); + if (accountTransaction.isAfter(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.last.transaction.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + } + + validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); + this.status = SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue(); + + final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); + final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); + this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); + + /* + * // withdraw deposit amount before closing the account final Money + * transactionAmountMoney = Money.of(this.currency, + * this.getAccountBalance()); final SavingsAccountTransaction withdraw = + * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, + * closedDate, transactionAmountMoney, new Date()); + * this.transactions.add(withdraw); + */ + actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); + actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); + actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.toString(fmt)); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = closedDate.toDate(); + this.closedBy = currentUser; + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + + } + + @Override + public Money activateWithBalance() { + return Money.of(this.currency, this.minRequiredOpeningBalance); + } + + protected void processAccountUponActivation(final DateTimeFormatter fmt, final AppUser user) { + final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); + if (minRequiredOpeningBalance.isGreaterThanZero()) { + final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationLocalDate(), + minRequiredOpeningBalance.getAmount(), null, new Date(), user); + deposit(transactionDTO); + + // update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(Money.zero(this.currency), DateUtils.getLocalDateOfTenant()); + } + } + + public void close(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate, + final Map actualChanges) { + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME + SavingsApiConstants.closeAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.MATURED.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.in.matured.state"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); + + if (closedDate.isBefore(getActivationLocalDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.activation.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + if (closedDate.isBefore(maturityDate())) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.account.maturity.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + if (closedDate.isAfter(tenantsTodayDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("cannot.be.a.future.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + final List savingsAccountTransactions = retreiveListOfTransactions(); + if (savingsAccountTransactions.size() > 0) { + final SavingsAccountTransaction accountTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1); + if (accountTransaction.isAfter(closedDate)) { + baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(closedDate) + .failWithCode("must.be.after.last.transaction.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + } + + validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_CLOSE_ACCOUNT, closedDate); + this.status = SavingsAccountStatusType.CLOSED.getValue(); + + final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); + final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); + this.accountTermAndPreClosure.updateOnAccountClosureStatus(onClosureType); + + /* + * // withdraw deposit amount before closing the account final Money + * transactionAmountMoney = Money.of(this.currency, + * this.getAccountBalance()); final SavingsAccountTransaction withdraw = + * SavingsAccountTransaction.withdrawal(this, office(), paymentDetail, + * closedDate, transactionAmountMoney, new Date()); + * this.transactions.add(withdraw); + */ + + actualChanges.put(SavingsApiConstants.statusParamName, SavingsEnumerations.status(this.status)); + actualChanges.put(SavingsApiConstants.localeParamName, command.locale()); + actualChanges.put(SavingsApiConstants.dateFormatParamName, command.dateFormat()); + actualChanges.put(SavingsApiConstants.closedOnDateParamName, closedDate.toString(fmt)); + + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = closedDate.toDate(); + this.closedBy = currentUser; + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + } + + public void postMaturityInterest(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + final LocalDate interestPostingUpToDate = maturityDate(); + final MathContext mc = MathContext.DECIMAL64; + boolean isInterestTransfer = false; + final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + + Money interestPostedToDate = Money.zero(this.currency); + + boolean recalucateDailyBalanceDetails = false; + + for (final PostingPeriod interestPostingPeriod : postingPeriods) { + + LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); + interestPostingTransactionDate = interestPostingTransactionDate.isAfter(interestPostingUpToDate) ? interestPostingUpToDate + : interestPostingTransactionDate; + final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); + + interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod); + + final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); + if (postingTransaction == null) { + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), + interestPostingTransactionDate, interestEarnedToBePostedForPeriod); + this.transactions.add(newPostingTransaction); + recalucateDailyBalanceDetails = true; + } else { + final boolean correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod); + if (correctionRequired) { + postingTransaction.reverse(); + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), + interestPostingTransactionDate, interestEarnedToBePostedForPeriod); + this.transactions.add(newPostingTransaction); + recalucateDailyBalanceDetails = true; + } + } + } + + if (recalucateDailyBalanceDetails) { + // update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate); + } + + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + } + + public void postPreMaturityInterest(final LocalDate accountCloseDate, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + + final Money interestPostedToDate = totalInterestPosted(); + // calculate interest before one day of closure date + final LocalDate interestCalculatedToDate = accountCloseDate.minusDays(1); + final Money interestOnMaturity = calculatePreMatureInterest(interestCalculatedToDate, + retreiveOrderedNonInterestPostingTransactions(), isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + + boolean recalucateDailyBalance = false; + + // post remaining interest + final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate); + if (!remainigInterestToBePosted.isZero()) { + final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), + accountCloseDate, remainigInterestToBePosted); + this.transactions.add(newPostingTransaction); + recalucateDailyBalance = true; + } + + if (recalucateDailyBalance) { + // update existing transactions so derived balance fields are + // correct. + recalculateDailyBalances(Money.zero(this.currency), accountCloseDate); + } + + this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); + this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate); + } + + public BigDecimal calculatePreMatureAmount(final LocalDate preMatureDate, final boolean isPreMatureClosure, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + + final Money interestPostedToDate = totalInterestPosted().copy(); + + final Money interestEarnedTillDate = calculatePreMatureInterest(preMatureDate, retreiveOrderedNonInterestPostingTransactions(), + isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + + final Money accountBalance = Money.of(getCurrency(), getAccountBalance()); + final Money maturityAmount = accountBalance.minus(interestPostedToDate).plus(interestEarnedTillDate); + + return maturityAmount.getAmount(); + } + + private Money calculatePreMatureInterest(final LocalDate preMatureDate, final List transactions, + final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, + final Integer financialYearBeginningMonth) { + final MathContext mc = MathContext.DECIMAL64; + final List postingPeriods = calculateInterestPayable(mc, preMatureDate, transactions, isPreMatureClosure, + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + + Money interestOnMaturity = Money.zero(this.currency); + + for (final PostingPeriod interestPostingPeriod : postingPeriods) { + final Money interestEarnedForPeriod = interestPostingPeriod.getInterestEarned(); + interestOnMaturity = interestOnMaturity.plus(interestEarnedForPeriod); + } + this.summary.updateFromInterestPeriodSummaries(this.currency, postingPeriods); + return interestOnMaturity; + } + + @Override + public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean isInterestTransfer, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); + super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + } + + @Override + public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); + return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth); + } + + private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { + LocalDate interestPostingUpToDate = interestPostingDate; + final LocalDate uptoMaturityDate = interestCalculatedUpto(); + if (uptoMaturityDate != null && uptoMaturityDate.isBefore(interestPostingDate)) { + interestPostingUpToDate = uptoMaturityDate; + } + return interestPostingUpToDate; + } + + public LocalDate maturityDate() { + return this.accountTermAndPreClosure.getMaturityLocalDate(); + } + + public BigDecimal maturityAmount() { + return this.accountTermAndPreClosure.maturityAmount(); + } + + private LocalDate interestCalculatedUpto() { + LocalDate uptoMaturityDate = calculateMaturityDate(); + if (uptoMaturityDate != null) { + // interest should not be calculated for maturity day + uptoMaturityDate = uptoMaturityDate.minusDays(1); + } + return uptoMaturityDate; + } + + private Money totalInterestPosted() { + Money interestPostedToDate = Money.zero(this.currency); + for (final SavingsAccountTransaction transaction : this.transactions) { + if (transaction.isInterestPostingAndNotReversed()) { + interestPostedToDate = interestPostedToDate.plus(transaction.getAmount(currency)); + } + } + + return interestPostedToDate; + } + + @Override + public Map activate(final AppUser currentUser, final JsonCommand command, final LocalDate tenantsTodayDate) { + + final Map actualChanges = super.activate(currentUser, command, tenantsTodayDate); + + if (accountTermAndPreClosure.isAfterExpectedFirstDepositDate(getActivationLocalDate())) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale()); + final String dateAsString = formatter.print(this.accountTermAndPreClosure.getExpectedFirstDepositOnDate()); + baseDataValidator.reset().parameter(DepositsApiConstants.activatedOnDateParamName).value(dateAsString) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.expected.first.deposit.date"); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + return actualChanges; + } + + protected List sortTransactions(final List transactions) { + final List listOfTransactionsSorted = new ArrayList<>(); + listOfTransactionsSorted.addAll(transactions); + + final SavingsAccountTransactionComparator transactionComparator = new SavingsAccountTransactionComparator(); + Collections.sort(listOfTransactionsSorted, transactionComparator); + return listOfTransactionsSorted; + } + + @Override + public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO) { + + if (isAccountMatured()) { + final String defaultUserMessage = "Transaction is not allowed. Account is matured."; + final ApiParameterError error = ApiParameterError.parameterError( + "error.msg.recurring.deposit.account.transaction.account.is.matured", defaultUserMessage, "transactionDate", + transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter())); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (!isBeforeMaturityDate(transactionDTO.getTransactionDate())) { + final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account maturity date."; + final ApiParameterError error = ApiParameterError.parameterError( + "error.msg.recurring.deposit.account.transaction.date.is.after.account.maturity.date", defaultUserMessage, + "transactionDate", transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter())); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + if (isBeforeDepositStartDate(transactionDTO.getTransactionDate())) { + final String defaultUserMessage = "Transaction is not allowed. Transaction date is on or after account activation and deposit start date."; + final ApiParameterError error = ApiParameterError.parameterError( + "error.msg.recurring.deposit.account.transaction.date.is.before.account.activation.or.deposit.date", + defaultUserMessage, "transactionDate", transactionDTO.getTransactionDate().toString(transactionDTO.getFormatter())); + + final List dataValidationErrors = new ArrayList<>(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + final SavingsAccountTransaction transaction = super.deposit(transactionDTO); + + return transaction; + } + + public void handleScheduleInstallments(final SavingsAccountTransaction transaction) { + + final LocalDate transactionDate = transaction.transactionLocalDate(); + Money transactionAmountUnprocessed = transaction.getAmount(getCurrency()); + + for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { + if (currentInstallment.isNotFullyPaidOff() && transactionAmountUnprocessed.isGreaterThanZero()) { + if (!this.adjustAdvanceTowardsFuturePayments() && currentInstallment.dueDate().isAfter(transactionDate)) { + transactionAmountUnprocessed = Money.zero(getCurrency()); + } + transactionAmountUnprocessed = handleInstallmentTransaction(currentInstallment, transactionAmountUnprocessed, + transactionDate); + } + } + + } + + public void updateScheduleInstallments() { + + // reset all installments to process from the beginning + for (RecurringDepositScheduleInstallment currentInstallment : depositScheduleInstallments()) { + currentInstallment.resetDerivedFields(); + } + + final List orderedDepositTransactions = retreiveOrderedDepositTransactions(); + for (SavingsAccountTransaction transaction : orderedDepositTransactions) { + handleScheduleInstallments(transaction); + } + } + + private List retreiveOrderedDepositTransactions() { + final List listOfTransactionsSorted = retreiveListOfTransactions(); + + final List orderedDepositTransactions = new ArrayList<>(); + + for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) { + if (transaction.isDepositAndNotReversed()) { + orderedDepositTransactions.add(transaction); + } + } + + return orderedDepositTransactions; + } + + /** + * This method is responsible for checking if the current transaction is 'an + * advance/early payment' based on the details passed through. + * + * Default implementation is check transaction date is before installment + * due date. + */ + protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex, + final List installments, final LocalDate transactionDate) { + + final RecurringDepositScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex); + + return transactionDate.isBefore(currentInstallment.dueDate()); + } + + private Money handleInstallmentTransaction(final RecurringDepositScheduleInstallment currentInstallment, + final Money transactionAmountUnprocessed, final LocalDate transactionDate) { + + Money transactionAmountRemaining = transactionAmountUnprocessed; + Money depositAmountPortion = Money.zero(transactionAmountRemaining.getCurrency()); + + depositAmountPortion = currentInstallment.payInstallment(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(depositAmountPortion); + + return transactionAmountRemaining; + + } + + private boolean isAccountMatured() { + return SavingsAccountStatusType.fromInt(status).isMatured(); + } + + private boolean isBeforeMaturityDate(final LocalDate compareDate) { + final LocalDate maturityDate = this.maturityDate(); + return compareDate.isBefore(maturityDate); + } + + private boolean isBeforeDepositStartDate(LocalDate compareDate) { + return compareDate.isBefore(depositStartDate()); + } + + public void validateDomainRules() { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + validateDomainRules(baseDataValidator); + super.validateInterestPostingAndCompoundingPeriodTypes(baseDataValidator); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + private void validateDomainRules(final DataValidatorBuilder baseDataValidator) { + + final boolean isMinTermGreaterThanMax = this.accountTermAndPreClosure.depositTermDetail() + .isMinDepositTermGreaterThanMaxDepositTerm(); + // deposit period should be within min and max deposit term + if (isMinTermGreaterThanMax) { + final Integer maxTerm = this.accountTermAndPreClosure.depositTermDetail().maxDepositTerm(); + baseDataValidator.reset().parameter(maxDepositTermParamName).value(maxTerm) + .failWithCodeNoParameterAddedToErrorCode("max.term.lessthan.min.term"); + } + + 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 (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()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "expected.first.deposit.date.must.be.after.account.submitted.or.activation.date"); + } + + // FIXME: Handle this scenario + /* + * //final boolean recurringFrequencyBeforeDepositPeriod = + * recurringFrequencyBeforeDepositPeriod(); + * + * if (!recurringFrequencyBeforeDepositPeriod) { + * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + * "recurring.frequency.not.before.deposit.period"); } + */ + } + + public void validateApplicableInterestRate() { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RECURRING_DEPOSIT_ACCOUNT_RESOURCE_NAME); + + if (this.chart != null) { + final LocalDate chartFromDate = this.chart.getFromDateAsLocalDate(); + LocalDate chartEndDate = this.chart.getEndDateAsLocalDate(); + chartEndDate = chartEndDate == null ? DateUtils.getLocalDateOfTenant() : chartEndDate; + + final LocalDateInterval chartInterval = LocalDateInterval.create(chartFromDate, chartEndDate); + if (!chartInterval.contains(accountSubmittedOrActivationDate())) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("no.valid.interest.rate.slab.available.for.date.range"); + } + + final BigDecimal maturityAmount = this.accountTermAndPreClosure.maturityAmount(); + BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(maturityAmount, depositStartDate(), + calculateMaturityDate(), this.client); + + if (applicableInterestRate.equals(BigDecimal.ZERO)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period"); + } + + } else if (this.nominalAnnualInterestRate == null || this.nominalAnnualInterestRate.compareTo(BigDecimal.ZERO) == 0) { + baseDataValidator.reset().parameter(DepositsApiConstants.nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate) + .failWithCodeNoParameterAddedToErrorCode("interest.chart.or.nominal.interest.rate.required"); + } + + /** + * final boolean recurringFrequencyBeforeDepositPeriod = + * recurringFrequencyBeforeDepositPeriod(); + * + * if (!recurringFrequencyBeforeDepositPeriod) { + * baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + * "recurring.frequency.not.before.deposit.period"); } + **/ + } + + public boolean isReinvestOnClosure() { + return this.accountTermAndPreClosure.isReinvestOnClosure(); + } + + public boolean isTransferToSavingsOnClosure() { + return this.accountTermAndPreClosure.isTransferToSavingsOnClosure(); + } + + public RecurringDepositAccount reInvest(BigDecimal depositAmount) { + + final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure.copy(depositAmount); + final DepositAccountRecurringDetail recurringDetail = this.recurringDetail.copy(); + final SavingsProduct product = this.product; + final InterestRateChart productChart = product.applicableChart(getClosedOnDate()); + final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart.from(productChart); + final String accountNumber = null; + final String externalId = this.externalId; + final AccountType accountType = AccountType.fromInt(this.accountType); + final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType); + final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType + .fromInt(this.interestCompoundingPeriodType); + final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType); + final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType + .fromInt(this.interestCalculationDaysInYearType); + final BigDecimal minRequiredOpeningBalance = depositAmount; + final BigDecimal interestRate = BigDecimal.ZERO; + final Set savingsAccountCharges = null; + final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType); + final Integer lockinPeriodFrequency = this.lockinPeriodFrequency; + final boolean withdrawalFeeApplicableForTransfer = false; + + LocalDate now = DateUtils.getLocalDateOfTenant(); + + newAccountTermAndPreClosure.updateExpectedFirstDepositDate(now); + + RecurringDepositAccount rdAccount = RecurringDepositAccount.createNewActivatedAccount(client, group, product, savingsOfficer, + accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType, + postingPeriodType, interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, + lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, newAccountTermAndPreClosure, + recurringDetail, newChart); + + rdAccount.setDatesFrom(now); + newAccountTermAndPreClosure.updateAccountReference(rdAccount); + recurringDetail.updateAccountReference(rdAccount); + + return rdAccount; + + } + + private boolean firstDepositDateBeforeAccountSubmittedOrActivationDate() { + final LocalDate expectedFirstDepositLocalDate = accountTermAndPreClosure.getExpectedFirstDepositOnDate(); + if (expectedFirstDepositLocalDate == null) return false; + return expectedFirstDepositLocalDate.isBefore(accountSubmittedOrActivationDate()); + } + + public void setDatesFrom(final LocalDate now) { + this.rejectedOnDate = null; + this.rejectedBy = null; + this.withdrawnOnDate = null; + this.withdrawnBy = null; + this.closedOnDate = null; + this.closedBy = null; + + this.activatedBy = null; + this.lockedInUntilDate = null; + + this.activatedOnDate = now.toDate(); + } + + @Override + protected boolean isTransferInterestToOtherAccount() { + return this.accountTermAndPreClosure.isTransferInterestToLinkedAccount(); + } + + public void generateSchedule(final PeriodFrequencyType frequency, final Integer recurringEvery, final Calendar calendar) { + final List depositScheduleInstallments = depositScheduleInstallments(); + depositScheduleInstallments.clear(); + LocalDate installmentDate = null; + if (this.isCalendarInherited()) { + installmentDate = CalendarUtils.getNextScheduleDate(calendar, accountSubmittedOrActivationDate()); + } else { + installmentDate = depositStartDate(); + } + + final LocalDate maturityDate = calculateMaturityDate(); + int installmentNumber = 1; + 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); + installmentNumber += 1; + } + } + + private List depositScheduleInstallments() { + if (this.depositScheduleInstallments == null) { + this.depositScheduleInstallments = new ArrayList<>(); + } + return this.depositScheduleInstallments; + } + + public boolean isCalendarInherited() { + return this.recurringDetail.isCalendarInherited(); + } + + public void updateOverduePayments(final LocalDate todayDate) { + LocalDate overdueUptoDate = this.maturityDate(); + if (overdueUptoDate.isAfter(todayDate)) { + overdueUptoDate = todayDate; + } + + final List installments = depositScheduleInstallments(); + int noOfOverdueInstallments = 0; + Money totalOverdueAmount = Money.zero(getCurrency()); + for (RecurringDepositScheduleInstallment installment : installments) { + if (installment.isNotFullyPaidOff() && overdueUptoDate.isAfter(installment.dueDate())) { + noOfOverdueInstallments++; + totalOverdueAmount = totalOverdueAmount.plus(installment.getDepositAmountOutstanding(getCurrency())); + } + } + this.recurringDetail.updateOverdueDetails(noOfOverdueInstallments, totalOverdueAmount); + } + + @Override + public boolean allowWithdrawal() { + return this.recurringDetail.allowWithdrawal(); + } + + public boolean adjustAdvanceTowardsFuturePayments() { + return this.recurringDetail.adjustAdvanceTowardsFuturePayments(); + } + + @Override + public boolean isTransactionsAllowed() { + return isActive() || isAccountMatured(); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java index f9ac2e9c5..dc3b04fb6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java @@ -229,7 +229,7 @@ public class SavingsAccountAssembler { } else { enforceMinRequiredBalance = product.isMinRequiredBalanceEnforced(); } - + BigDecimal minRequiredBalance = null; if (command.parameterExists(minRequiredBalanceParamName)) { minRequiredBalance = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredBalanceParamName); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index 2cad2e18e..0b0ba4b59 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -97,7 +97,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable { @Temporal(TemporalType.TIMESTAMP) @Column(name = "created_date", nullable = false) private final Date createdDate; - + @ManyToOne @JoinColumn(name = "appuser_id", nullable = true) private AppUser appUser; @@ -157,8 +157,8 @@ public final class SavingsAccountTransaction extends AbstractPersistable { isReversed, appUser); } - public static SavingsAccountTransaction initiateTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final AppUser appUser) { + public static SavingsAccountTransaction initiateTransfer(final SavingsAccount savingsAccount, final Office office, + final LocalDate date, final AppUser appUser) { final boolean isReversed = false; final PaymentDetail paymentDetail = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, @@ -167,7 +167,7 @@ public final class SavingsAccountTransaction extends AbstractPersistable { } public static SavingsAccountTransaction approveTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final AppUser appUser) { + final AppUser appUser) { final boolean isReversed = false; final PaymentDetail paymentDetail = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, @@ -175,8 +175,8 @@ public final class SavingsAccountTransaction extends AbstractPersistable { .getAccountBalance(), isReversed, appUser); } - public static SavingsAccountTransaction withdrawTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final AppUser appUser) { + public static SavingsAccountTransaction withdrawTransfer(final SavingsAccount savingsAccount, final Office office, + final LocalDate date, final AppUser appUser) { final boolean isReversed = false; final PaymentDetail paymentDetail = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, @@ -196,8 +196,8 @@ public final class SavingsAccountTransaction extends AbstractPersistable { } private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, - final Integer typeOf, final LocalDate transactionLocalDate, final Date createdDate, final Money amount, final boolean isReversed, - final AppUser appUser) { + final Integer typeOf, final LocalDate transactionLocalDate, final Date createdDate, final Money amount, + final boolean isReversed, final AppUser appUser) { this(savingsAccount, office, paymentDetail, typeOf, transactionLocalDate, createdDate, amount.getAmount(), isReversed, appUser); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsHelper.java index df2d493c0..9520b5c1b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsHelper.java @@ -60,70 +60,65 @@ public final class SavingsHelper { LocalDate periodEndDate = interestPostingUpToDate; final Integer monthOfYear = periodStartDate.getMonthOfYear(); financialYearBeginningMonth--; - if(financialYearBeginningMonth == 0) - financialYearBeginningMonth = 12; - - final ArrayList quarterlyDates = new ArrayList<>(); - quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).dayOfMonth().withMaximumValue()); - quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth) - .plusMonths(3).withYear(periodStartDate.getYear()).dayOfMonth().withMaximumValue()); - quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth) - .plusMonths(6).withYear(periodStartDate.getYear()).dayOfMonth().withMaximumValue()); - quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth) - .plusMonths(9).withYear(periodStartDate.getYear()).dayOfMonth().withMaximumValue()); - Collections.sort(quarterlyDates); - - final ArrayList biannualDates = new ArrayList<>(); - biannualDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).dayOfMonth().withMaximumValue()); - biannualDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).plusMonths(6) - .withYear(periodStartDate.getYear()).dayOfMonth().withMaximumValue()); - Collections.sort(biannualDates); - - boolean isEndDateSet = false; - - switch (interestPostingPeriodType) { - case INVALID: - break; - case MONTHLY: - // produce period end date on last day of current month - periodEndDate = periodStartDate.dayOfMonth().withMaximumValue(); - break; - case QUATERLY: - for(LocalDate quarterlyDate : quarterlyDates) { - if(quarterlyDate.isAfter(periodStartDate)) { - periodEndDate = quarterlyDate; - isEndDateSet = true; - break; - } - } - - if(!isEndDateSet) - periodEndDate = quarterlyDates.get(0).plusYears(1).dayOfMonth().withMaximumValue(); - break; - case BIANNUAL: - for(LocalDate biannualDate : biannualDates) { - if(biannualDate.isAfter(periodStartDate)) { - periodEndDate = biannualDate; - isEndDateSet = true; - break; - } - } - - if(!isEndDateSet) - periodEndDate = biannualDates.get(0).plusYears(1).dayOfMonth().withMaximumValue(); - break; - case ANNUAL: - if (financialYearBeginningMonth < monthOfYear) { - periodEndDate = periodStartDate - .withMonthOfYear(financialYearBeginningMonth); - periodEndDate = periodEndDate.plusYears(1); - } else { - periodEndDate = periodStartDate - .withMonthOfYear(financialYearBeginningMonth); - } - periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); - break; - } + if (financialYearBeginningMonth == 0) financialYearBeginningMonth = 12; + + final ArrayList quarterlyDates = new ArrayList<>(); + quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).dayOfMonth().withMaximumValue()); + quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).plusMonths(3).withYear(periodStartDate.getYear()) + .dayOfMonth().withMaximumValue()); + quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).plusMonths(6).withYear(periodStartDate.getYear()) + .dayOfMonth().withMaximumValue()); + quarterlyDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).plusMonths(9).withYear(periodStartDate.getYear()) + .dayOfMonth().withMaximumValue()); + Collections.sort(quarterlyDates); + + final ArrayList biannualDates = new ArrayList<>(); + biannualDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).dayOfMonth().withMaximumValue()); + biannualDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).plusMonths(6).withYear(periodStartDate.getYear()) + .dayOfMonth().withMaximumValue()); + Collections.sort(biannualDates); + + boolean isEndDateSet = false; + + switch (interestPostingPeriodType) { + case INVALID: + break; + case MONTHLY: + // produce period end date on last day of current month + periodEndDate = periodStartDate.dayOfMonth().withMaximumValue(); + break; + case QUATERLY: + for (LocalDate quarterlyDate : quarterlyDates) { + if (quarterlyDate.isAfter(periodStartDate)) { + periodEndDate = quarterlyDate; + isEndDateSet = true; + break; + } + } + + if (!isEndDateSet) periodEndDate = quarterlyDates.get(0).plusYears(1).dayOfMonth().withMaximumValue(); + break; + case BIANNUAL: + for (LocalDate biannualDate : biannualDates) { + if (biannualDate.isAfter(periodStartDate)) { + periodEndDate = biannualDate; + isEndDateSet = true; + break; + } + } + + if (!isEndDateSet) periodEndDate = biannualDates.get(0).plusYears(1).dayOfMonth().withMaximumValue(); + break; + case ANNUAL: + if (financialYearBeginningMonth < monthOfYear) { + periodEndDate = periodStartDate.withMonthOfYear(financialYearBeginningMonth); + periodEndDate = periodEndDate.plusYears(1); + } else { + periodEndDate = periodStartDate.withMonthOfYear(financialYearBeginningMonth); + } + periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); + break; + } // interest posting always occurs on next day after the period end date. periodEndDate = periodEndDate.plusDays(1); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsOfficerAssignmentHistory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsOfficerAssignmentHistory.java index ba2cd3a2f..05e4c851b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsOfficerAssignmentHistory.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsOfficerAssignmentHistory.java @@ -32,7 +32,7 @@ public class SavingsOfficerAssignmentHistory extends AbstractAuditableCustom