FINERACT-2011: Savings Account - transaction order is incorrectly mixed after certain activities
This commit is contained in:
+7
-53
@@ -32,11 +32,13 @@ import java.time.temporal.TemporalAccessor;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.infrastructure.security.domain.BasicPasswordEncodablePlatformUser;
|
import org.apache.fineract.infrastructure.security.domain.BasicPasswordEncodablePlatformUser;
|
||||||
import org.apache.fineract.infrastructure.security.domain.PlatformUser;
|
import org.apache.fineract.infrastructure.security.domain.PlatformUser;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformPasswordEncoder;
|
import org.apache.fineract.infrastructure.security.service.PlatformPasswordEncoder;
|
||||||
@@ -268,27 +270,11 @@ public final class JsonCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean differenceExists(final TemporalAccessor baseValue, final TemporalAccessor workingCopyValue) {
|
private boolean differenceExists(final TemporalAccessor baseValue, final TemporalAccessor workingCopyValue) {
|
||||||
boolean differenceExists = false;
|
return !Objects.equals(baseValue, workingCopyValue);
|
||||||
|
|
||||||
if (baseValue != null) {
|
|
||||||
differenceExists = !baseValue.equals(workingCopyValue);
|
|
||||||
} else {
|
|
||||||
differenceExists = workingCopyValue != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return differenceExists;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean differenceExists(final String baseValue, final String workingCopyValue) {
|
private boolean differenceExists(final String baseValue, final String workingCopyValue) {
|
||||||
boolean differenceExists = false;
|
return !Objects.equals(baseValue, workingCopyValue);
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(baseValue)) {
|
|
||||||
differenceExists = !baseValue.equals(workingCopyValue);
|
|
||||||
} else {
|
|
||||||
differenceExists = StringUtils.isNotBlank(workingCopyValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
return differenceExists;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean differenceExists(final String[] baseValue, final String[] workingCopyValue) {
|
private boolean differenceExists(final String[] baseValue, final String[] workingCopyValue) {
|
||||||
@@ -298,47 +284,15 @@ public final class JsonCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean differenceExists(final Number baseValue, final Number workingCopyValue) {
|
private boolean differenceExists(final Number baseValue, final Number workingCopyValue) {
|
||||||
boolean differenceExists = false;
|
return !Objects.equals(baseValue, workingCopyValue);
|
||||||
|
|
||||||
if (baseValue != null) {
|
|
||||||
if (workingCopyValue != null) {
|
|
||||||
differenceExists = !baseValue.equals(workingCopyValue);
|
|
||||||
} else {
|
|
||||||
differenceExists = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
differenceExists = workingCopyValue != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return differenceExists;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean differenceExists(final BigDecimal baseValue, final BigDecimal workingCopyValue) {
|
private boolean differenceExists(final BigDecimal baseValue, final BigDecimal workingCopyValue) {
|
||||||
boolean differenceExists = false;
|
return !MathUtil.isEqualTo(baseValue, workingCopyValue);
|
||||||
|
|
||||||
if (baseValue != null) {
|
|
||||||
if (workingCopyValue != null) {
|
|
||||||
differenceExists = baseValue.compareTo(workingCopyValue) != 0;
|
|
||||||
} else {
|
|
||||||
differenceExists = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
differenceExists = workingCopyValue != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return differenceExists;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean differenceExists(final Boolean baseValue, final Boolean workingCopyValue) {
|
private boolean differenceExists(final Boolean baseValue, final Boolean workingCopyValue) {
|
||||||
boolean differenceExists = false;
|
return !Objects.equals(baseValue, workingCopyValue);
|
||||||
|
|
||||||
if (baseValue != null) {
|
|
||||||
differenceExists = !baseValue.equals(workingCopyValue);
|
|
||||||
} else {
|
|
||||||
differenceExists = workingCopyValue != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return differenceExists;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean parameterExists(final String parameterName) {
|
public boolean parameterExists(final String parameterName) {
|
||||||
|
|||||||
+1
-4
@@ -64,10 +64,9 @@ public class GlobalConfigurationProperty extends AbstractPersistableCustom {
|
|||||||
private boolean isTrapDoor;
|
private boolean isTrapDoor;
|
||||||
|
|
||||||
public Map<String, Object> update(final JsonCommand command) {
|
public Map<String, Object> update(final JsonCommand command) {
|
||||||
|
|
||||||
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
|
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
|
||||||
|
|
||||||
if (this.isTrapDoor == true) {
|
if (this.isTrapDoor) {
|
||||||
throw new GlobalConfigurationPropertyCannotBeModfied(this.getId());
|
throw new GlobalConfigurationPropertyCannotBeModfied(this.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +108,6 @@ public class GlobalConfigurationProperty extends AbstractPersistableCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return actualChanges;
|
return actualChanges;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GlobalConfigurationProperty newSurveyConfiguration(final String name) {
|
public static GlobalConfigurationProperty newSurveyConfiguration(final String name) {
|
||||||
@@ -120,6 +118,5 @@ public class GlobalConfigurationProperty extends AbstractPersistableCustom {
|
|||||||
return new GlobalConfigurationPropertyData().setName(getName()).setEnabled(isEnabled()).setValue(getValue())
|
return new GlobalConfigurationPropertyData().setName(getName()).setEnabled(isEnabled()).setValue(getValue())
|
||||||
.setDateValue(getDateValue()).setStringValue(getStringValue()).setId(this.getId()).setDescription(this.description)
|
.setDateValue(getDateValue()).setStringValue(getStringValue()).setId(this.getId()).setDescription(this.description)
|
||||||
.setTrapDoor(this.isTrapDoor);
|
.setTrapDoor(this.isTrapDoor);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+104
-98
@@ -83,6 +83,7 @@ import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
|||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator;
|
import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator;
|
||||||
import org.apache.fineract.interoperation.domain.InteropIdentifier;
|
import org.apache.fineract.interoperation.domain.InteropIdentifier;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
@@ -510,6 +511,9 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer,
|
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer,
|
||||||
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill,
|
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill,
|
||||||
postReversals);
|
postReversals);
|
||||||
|
if (postingPeriods.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Money interestPostedToDate = Money.zero(this.currency);
|
Money interestPostedToDate = Money.zero(this.currency);
|
||||||
|
|
||||||
@@ -811,109 +815,103 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
|
boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
|
||||||
final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final boolean postReversals) {
|
final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final boolean postReversals) {
|
||||||
|
|
||||||
// no openingBalance concept supported yet but probably will to allow
|
// no openingBalance concept supported yet but probably will to allow for migrations.
|
||||||
// for migrations.
|
|
||||||
Money openingAccountBalance = null;
|
|
||||||
|
|
||||||
// Check global configurations and 'pivot' date is null
|
// Check global configurations and 'pivot' date is null
|
||||||
if (backdatedTxnsAllowedTill) {
|
Money openingAccountBalance = backdatedTxnsAllowedTill ? Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate())
|
||||||
openingAccountBalance = Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate());
|
: Money.zero(this.currency);
|
||||||
} else {
|
|
||||||
openingAccountBalance = Money.zero(this.currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update existing transactions so derived balance fields are
|
|
||||||
// correct.
|
|
||||||
|
|
||||||
|
// update existing transactions so derived balance fields are correct.
|
||||||
recalculateDailyBalances(openingAccountBalance, upToInterestCalculationDate, backdatedTxnsAllowedTill, postReversals);
|
recalculateDailyBalances(openingAccountBalance, upToInterestCalculationDate, backdatedTxnsAllowedTill, postReversals);
|
||||||
|
|
||||||
// 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);
|
|
||||||
List<LocalDate> postedAsOnDates = null;
|
|
||||||
if (backdatedTxnsAllowedTill) {
|
|
||||||
postedAsOnDates = getManualPostingDatesWithPivotConfig();
|
|
||||||
} else {
|
|
||||||
postedAsOnDates = getManualPostingDates();
|
|
||||||
}
|
|
||||||
if (postInterestOnDate != null) {
|
|
||||||
postedAsOnDates.add(postInterestOnDate);
|
|
||||||
}
|
|
||||||
final List<LocalDateInterval> postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(
|
|
||||||
getStartInterestCalculationDate(), upToInterestCalculationDate, postingPeriodType, financialYearBeginningMonth,
|
|
||||||
postedAsOnDates);
|
|
||||||
|
|
||||||
final List<PostingPeriod> allPostingPeriods = new ArrayList<>();
|
final List<PostingPeriod> allPostingPeriods = new ArrayList<>();
|
||||||
|
if (hasInterestCalculation() || hasOverdraftInterestCalculation()) {
|
||||||
|
// 1. default to calculate interest based on entire history OR
|
||||||
|
// 2. determine latest 'posting period' and find interest credited to that period
|
||||||
|
|
||||||
Money periodStartingBalance;
|
// A generate list of EndOfDayBalances (not including interest postings)
|
||||||
if (this.startInterestCalculationDate != null && !this.getStartInterestCalculationDate().equals(this.getActivationDate())) {
|
final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType
|
||||||
LocalDate startInterestCalculationDate = this.startInterestCalculationDate;
|
.fromInt(this.interestPostingPeriodType);
|
||||||
SavingsAccountTransaction transaction = null;
|
|
||||||
|
final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType
|
||||||
|
.fromInt(this.interestCompoundingPeriodType);
|
||||||
|
|
||||||
|
final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType
|
||||||
|
.fromInt(this.interestCalculationDaysInYearType);
|
||||||
|
List<LocalDate> postedAsOnDates = null;
|
||||||
if (backdatedTxnsAllowedTill) {
|
if (backdatedTxnsAllowedTill) {
|
||||||
transaction = findLastFilteredTransactionWithPivotConfig(startInterestCalculationDate);
|
postedAsOnDates = getManualPostingDatesWithPivotConfig();
|
||||||
} else {
|
} else {
|
||||||
transaction = findLastTransaction(startInterestCalculationDate);
|
postedAsOnDates = getManualPostingDates();
|
||||||
}
|
}
|
||||||
|
if (postInterestOnDate != null) {
|
||||||
|
postedAsOnDates.add(postInterestOnDate);
|
||||||
|
}
|
||||||
|
final List<LocalDateInterval> postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(
|
||||||
|
getStartInterestCalculationDate(), upToInterestCalculationDate, postingPeriodType, financialYearBeginningMonth,
|
||||||
|
postedAsOnDates);
|
||||||
|
|
||||||
if (transaction == null) {
|
Money periodStartingBalance;
|
||||||
|
if (this.startInterestCalculationDate != null && !this.getStartInterestCalculationDate().equals(this.getActivationDate())) {
|
||||||
|
LocalDate startInterestCalculationDate = this.startInterestCalculationDate;
|
||||||
|
SavingsAccountTransaction transaction = null;
|
||||||
|
if (backdatedTxnsAllowedTill) {
|
||||||
|
transaction = findLastFilteredTransactionWithPivotConfig(startInterestCalculationDate);
|
||||||
|
} else {
|
||||||
|
transaction = findLastTransaction(startInterestCalculationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transaction == null) {
|
||||||
|
periodStartingBalance = Money.zero(this.currency);
|
||||||
|
} else {
|
||||||
|
periodStartingBalance = Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
periodStartingBalance = Money.zero(this.currency);
|
periodStartingBalance = Money.zero(this.currency);
|
||||||
} else {
|
|
||||||
periodStartingBalance = Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate());
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
periodStartingBalance = Money.zero(this.currency);
|
final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType
|
||||||
|
.fromInt(this.interestCalculationType);
|
||||||
|
final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, upToInterestCalculationDate);
|
||||||
|
final BigDecimal overdraftInterestRateAsFraction = getEffectiveOverdraftInterestRateAsFraction(mc);
|
||||||
|
final Collection<Long> interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(getId());
|
||||||
|
final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation());
|
||||||
|
final Money minOverdraftForInterestCalculation = Money.of(getCurrency(), this.minOverdraftForInterestCalculation);
|
||||||
|
|
||||||
|
for (final LocalDateInterval periodInterval : postingPeriodIntervals) {
|
||||||
|
|
||||||
|
boolean isUserPosting = false;
|
||||||
|
if (postedAsOnDates.contains(periodInterval.endDate().plusDays(1))) {
|
||||||
|
isUserPosting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PostingPeriod postingPeriod = null;
|
||||||
|
List<SavingsAccountTransaction> orderedNonInterestPostingTransactions = null;
|
||||||
|
if (backdatedTxnsAllowedTill) {
|
||||||
|
orderedNonInterestPostingTransactions = retreiveOrderedNonInterestPostingSavingsTransactionsWithPivotConfig();
|
||||||
|
} else {
|
||||||
|
orderedNonInterestPostingTransactions = retreiveOrderedNonInterestPostingTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SavingsAccountTransactionDetailsForPostingPeriod> savingsAccountTransactionDetailsForPostingPeriod = toSavingsAccountTransactionDetailsForPostingPeriodList(
|
||||||
|
orderedNonInterestPostingTransactions);
|
||||||
|
|
||||||
|
postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance,
|
||||||
|
savingsAccountTransactionDetailsForPostingPeriod, this.currency, compoundingPeriodType, interestCalculationType,
|
||||||
|
interestRateAsFraction, daysInYearType.getValue(), upToInterestCalculationDate, interestPostTransactions,
|
||||||
|
isInterestTransfer, minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||||
|
overdraftInterestRateAsFraction, minOverdraftForInterestCalculation, isUserPosting, financialYearBeginningMonth);
|
||||||
|
|
||||||
|
periodStartingBalance = postingPeriod.closingBalance();
|
||||||
|
|
||||||
|
allPostingPeriods.add(postingPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, getLockedInUntilDate(),
|
||||||
|
isTransferInterestToOtherAccount());
|
||||||
|
|
||||||
|
this.summary.updateFromInterestPeriodSummaries(this.currency, allPostingPeriods);
|
||||||
}
|
}
|
||||||
|
|
||||||
final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType);
|
|
||||||
final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, upToInterestCalculationDate);
|
|
||||||
final BigDecimal overdraftInterestRateAsFraction = getEffectiveOverdraftInterestRateAsFraction(mc);
|
|
||||||
final Collection<Long> interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(getId());
|
|
||||||
final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation());
|
|
||||||
final Money minOverdraftForInterestCalculation = Money.of(getCurrency(), this.minOverdraftForInterestCalculation);
|
|
||||||
|
|
||||||
for (final LocalDateInterval periodInterval : postingPeriodIntervals) {
|
|
||||||
|
|
||||||
boolean isUserPosting = false;
|
|
||||||
if (postedAsOnDates.contains(periodInterval.endDate().plusDays(1))) {
|
|
||||||
isUserPosting = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
PostingPeriod postingPeriod = null;
|
|
||||||
List<SavingsAccountTransaction> orderedNonInterestPostingTransactions = null;
|
|
||||||
if (backdatedTxnsAllowedTill) {
|
|
||||||
orderedNonInterestPostingTransactions = retreiveOrderedNonInterestPostingSavingsTransactionsWithPivotConfig();
|
|
||||||
} else {
|
|
||||||
orderedNonInterestPostingTransactions = retreiveOrderedNonInterestPostingTransactions();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SavingsAccountTransactionDetailsForPostingPeriod> savingsAccountTransactionDetailsForPostingPeriod = toSavingsAccountTransactionDetailsForPostingPeriodList(
|
|
||||||
orderedNonInterestPostingTransactions);
|
|
||||||
|
|
||||||
postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance,
|
|
||||||
savingsAccountTransactionDetailsForPostingPeriod, this.currency, compoundingPeriodType, interestCalculationType,
|
|
||||||
interestRateAsFraction, daysInYearType.getValue(), upToInterestCalculationDate, interestPostTransactions,
|
|
||||||
isInterestTransfer, minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd,
|
|
||||||
overdraftInterestRateAsFraction, minOverdraftForInterestCalculation, isUserPosting, financialYearBeginningMonth);
|
|
||||||
|
|
||||||
periodStartingBalance = postingPeriod.closingBalance();
|
|
||||||
|
|
||||||
allPostingPeriods.add(postingPeriod);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.savingsHelper.calculateInterestForAllPostingPeriods(this.currency, allPostingPeriods, getLockedInUntilDate(),
|
|
||||||
isTransferInterestToOtherAccount());
|
|
||||||
|
|
||||||
this.summary.updateFromInterestPeriodSummaries(this.currency, allPostingPeriods);
|
|
||||||
|
|
||||||
if (backdatedTxnsAllowedTill) {
|
if (backdatedTxnsAllowedTill) {
|
||||||
this.summary.updateSummaryWithPivotConfig(this.currency, this.savingsAccountTransactionSummaryWrapper, null,
|
this.summary.updateSummaryWithPivotConfig(this.currency, this.savingsAccountTransactionSummaryWrapper, null,
|
||||||
this.savingsAccountTransactions);
|
this.savingsAccountTransactions);
|
||||||
@@ -933,6 +931,14 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
return this.nominalAnnualInterestRate.divide(BigDecimal.valueOf(100L), mc);
|
return this.nominalAnnualInterestRate.divide(BigDecimal.valueOf(100L), mc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasInterestCalculation() {
|
||||||
|
return !MathUtil.isEmpty(nominalAnnualInterestRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasOverdraftInterestCalculation() {
|
||||||
|
return isAllowOverdraft() && !MathUtil.isEmpty(getOverdraftLimit()) && !MathUtil.isEmpty(nominalAnnualInterestRateOverdraft);
|
||||||
|
}
|
||||||
|
|
||||||
protected List<SavingsAccountTransaction> retreiveOrderedNonInterestPostingTransactions() {
|
protected List<SavingsAccountTransaction> retreiveOrderedNonInterestPostingTransactions() {
|
||||||
final List<SavingsAccountTransaction> listOfTransactionsSorted = retrieveListOfTransactions();
|
final List<SavingsAccountTransaction> listOfTransactionsSorted = retrieveListOfTransactions();
|
||||||
|
|
||||||
@@ -982,7 +988,8 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
|
|
||||||
protected void recalculateDailyBalances(final Money openingAccountBalance, final LocalDate interestPostingUpToDate,
|
protected void recalculateDailyBalances(final Money openingAccountBalance, final LocalDate interestPostingUpToDate,
|
||||||
final boolean backdatedTxnsAllowedTill, boolean postReversals) {
|
final boolean backdatedTxnsAllowedTill, boolean postReversals) {
|
||||||
Money runningBalance = openingAccountBalance.copy();
|
Money runningBalance = openingAccountBalance;
|
||||||
|
boolean calculateInterest = hasInterestCalculation() || hasOverdraftInterestCalculation();
|
||||||
|
|
||||||
List<SavingsAccountTransaction> accountTransactionsSorted = null;
|
List<SavingsAccountTransaction> accountTransactionsSorted = null;
|
||||||
|
|
||||||
@@ -1017,15 +1024,14 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runningBalance = runningBalance.plus(transactionAmount);
|
runningBalance = runningBalance.plus(transactionAmount);
|
||||||
if (!transaction.getRunningBalance(this.currency).isEqualTo(transactionAmount)) {
|
transaction.setRunningBalance(runningBalance);
|
||||||
transaction.setRunningBalance(runningBalance);
|
|
||||||
|
if (MathUtil.isEmpty(overdraftAmount) && runningBalance.isLessThanZero() && !transaction.isAmountOnHold()) {
|
||||||
|
overdraftAmount = runningBalance.negated();
|
||||||
}
|
}
|
||||||
if (overdraftAmount.isZero() && runningBalance.isLessThanZero() && !transaction.isAmountOnHold()) {
|
if (!calculateInterest || transaction.getId() == null) {
|
||||||
overdraftAmount = overdraftAmount.plus(runningBalance.getAmount().negate());
|
|
||||||
}
|
|
||||||
if (transaction.getId() == null && overdraftAmount.isGreaterThanZero()) {
|
|
||||||
transaction.setOverdraftAmount(overdraftAmount);
|
transaction.setOverdraftAmount(overdraftAmount);
|
||||||
} else if (overdraftAmount.isNotEqualTo(transaction.getOverdraftAmount(this.currency))) {
|
} else if (!MathUtil.isEqualTo(overdraftAmount, transaction.getOverdraftAmount(this.currency))) {
|
||||||
SavingsAccountTransaction accountTransaction = SavingsAccountTransaction.copyTransaction(transaction);
|
SavingsAccountTransaction accountTransaction = SavingsAccountTransaction.copyTransaction(transaction);
|
||||||
if (transaction.isChargeTransaction()) {
|
if (transaction.isChargeTransaction()) {
|
||||||
Set<SavingsAccountChargePaidBy> chargesPaidBy = transaction.getSavingsAccountChargesPaid();
|
Set<SavingsAccountChargePaidBy> chargesPaidBy = transaction.getSavingsAccountChargesPaid();
|
||||||
@@ -1039,7 +1045,7 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
if (postReversals) {
|
if (postReversals) {
|
||||||
reversal = SavingsAccountTransaction.reversal(transaction);
|
reversal = SavingsAccountTransaction.reversal(transaction);
|
||||||
}
|
}
|
||||||
if (overdraftAmount.isGreaterThanZero()) {
|
if (MathUtil.isGreaterThanZero(overdraftAmount)) {
|
||||||
accountTransaction.setOverdraftAmount(overdraftAmount);
|
accountTransaction.setOverdraftAmount(overdraftAmount);
|
||||||
}
|
}
|
||||||
accountTransaction.setRunningBalance(runningBalance);
|
accountTransaction.setRunningBalance(runningBalance);
|
||||||
|
|||||||
+3
-2
@@ -63,6 +63,7 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati
|
|||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
|
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
|
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
|
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
|
||||||
@@ -579,8 +580,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
|||||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||||
|
|
||||||
if (savingsAccountData.getNominalAnnualInterestRate().compareTo(BigDecimal.ZERO) > 0 || (savingsAccountData.isAllowOverdraft()
|
if (MathUtil.isGreaterThanZero(savingsAccountData.getNominalAnnualInterestRate()) || (savingsAccountData.isAllowOverdraft()
|
||||||
&& savingsAccountData.getNominalAnnualInterestRateOverdraft().compareTo(BigDecimal.ZERO) > 0)) {
|
&& MathUtil.isGreaterThanZero(savingsAccountData.getNominalAnnualInterestRateOverdraft()))) {
|
||||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||||
updateExistingTransactionsDetails(savingsAccountData, existingTransactionIds, existingReversedTransactionIds);
|
updateExistingTransactionsDetails(savingsAccountData, existingTransactionIds, existingReversedTransactionIds);
|
||||||
|
|||||||
-50
@@ -3162,56 +3162,6 @@ public class ClientSavingsIntegrationTest {
|
|||||||
error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
|
error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSavingsAccountDepositAfterNegativeHoldAmount() {
|
|
||||||
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
|
||||||
|
|
||||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
|
||||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
|
||||||
|
|
||||||
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, "0", null, false, true, false);
|
|
||||||
Assertions.assertNotNull(savingsProductID);
|
|
||||||
|
|
||||||
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
|
|
||||||
this.savingsAccountHelper.approveSavings(savingsId);
|
|
||||||
HashMap savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
|
|
||||||
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
|
|
||||||
|
|
||||||
float balance = 0F;
|
|
||||||
float transactionAmount = 100F;
|
|
||||||
Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId,
|
|
||||||
String.valueOf(transactionAmount), SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
|
||||||
Assertions.assertNotNull(depositTransactionId);
|
|
||||||
balance = balance + transactionAmount;
|
|
||||||
HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
|
||||||
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after deposit");
|
|
||||||
Integer withdrawalTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId,
|
|
||||||
String.valueOf(transactionAmount), SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
|
||||||
Assertions.assertNotNull(withdrawalTransactionId);
|
|
||||||
balance = balance - transactionAmount;
|
|
||||||
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
|
||||||
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after withdrawal");
|
|
||||||
|
|
||||||
float holdAmount = 50F;
|
|
||||||
Integer holdTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, String.valueOf(holdAmount),
|
|
||||||
false, SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
|
||||||
Assertions.assertNotNull(holdTransactionId);
|
|
||||||
balance = balance - holdAmount;
|
|
||||||
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
|
||||||
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after hold amount");
|
|
||||||
this.savingsAccountHelper.releaseAmount(savingsId, holdTransactionId);
|
|
||||||
balance = balance + holdAmount;
|
|
||||||
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
|
||||||
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after release amount");
|
|
||||||
|
|
||||||
depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, String.valueOf(transactionAmount),
|
|
||||||
SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
|
||||||
Assertions.assertNotNull(depositTransactionId);
|
|
||||||
balance = balance + transactionAmount;
|
|
||||||
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
|
||||||
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after hold-release-deposit");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer createSavingsAccountDailyPostingOverdraft(final Integer clientID, final String startDate) {
|
private Integer createSavingsAccountDailyPostingOverdraft(final Integer clientID, final String startDate) {
|
||||||
final Integer savingsProductID = createSavingsProductDailyPostingOverdraft();
|
final Integer savingsProductID = createSavingsProductDailyPostingOverdraft();
|
||||||
Assertions.assertNotNull(savingsProductID);
|
Assertions.assertNotNull(savingsProductID);
|
||||||
|
|||||||
+230
@@ -0,0 +1,230 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.integrationtests;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
|
||||||
|
import io.restassured.builder.RequestSpecBuilder;
|
||||||
|
import io.restassured.builder.ResponseSpecBuilder;
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
import io.restassured.specification.RequestSpecification;
|
||||||
|
import io.restassured.specification.ResponseSpecification;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.CommonConstants;
|
||||||
|
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.PaymentTypeHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Order;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client Savings Integration Test for checking Savings Application.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "rawtypes" })
|
||||||
|
@Order(2)
|
||||||
|
public class SavingsAccountRecalculateBalanceTest {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SavingsAccountRecalculateBalanceTest.class);
|
||||||
|
public static final String DEPOSIT_AMOUNT = "2000";
|
||||||
|
public static final String WITHDRAW_AMOUNT = "1000";
|
||||||
|
public static final String WITHDRAW_AMOUNT_ADJUSTED = "500";
|
||||||
|
public static final String MINIMUM_OPENING_BALANCE = "1000.0";
|
||||||
|
public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
|
||||||
|
public static final String DATE_FORMAT = "dd MMMM yyyy";
|
||||||
|
|
||||||
|
private ResponseSpecification responseSpec;
|
||||||
|
private RequestSpecification requestSpec;
|
||||||
|
private SavingsAccountHelper savingsAccountHelper;
|
||||||
|
private SavingsProductHelper savingsProductHelper;
|
||||||
|
private SchedulerJobHelper scheduleJobHelper;
|
||||||
|
private PaymentTypeHelper paymentTypeHelper;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
Utils.initializeRESTAssured();
|
||||||
|
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||||
|
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||||
|
this.requestSpec.header("Fineract-Platform-TenantId", "default");
|
||||||
|
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||||
|
this.paymentTypeHelper = new PaymentTypeHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSavingsAccountDepositAfterNegativeHoldAmount() {
|
||||||
|
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||||
|
|
||||||
|
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||||
|
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||||
|
|
||||||
|
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, "0", null, false, true, false, null);
|
||||||
|
Assertions.assertNotNull(savingsProductID);
|
||||||
|
|
||||||
|
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
|
||||||
|
this.savingsAccountHelper.approveSavings(savingsId);
|
||||||
|
HashMap savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
|
||||||
|
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
|
||||||
|
|
||||||
|
float balance = 0F;
|
||||||
|
float transactionAmount = 100F;
|
||||||
|
Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId,
|
||||||
|
String.valueOf(transactionAmount), SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
Assertions.assertNotNull(depositTransactionId);
|
||||||
|
balance = balance + transactionAmount;
|
||||||
|
HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after deposit");
|
||||||
|
Integer withdrawalTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId,
|
||||||
|
String.valueOf(transactionAmount), SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
Assertions.assertNotNull(withdrawalTransactionId);
|
||||||
|
balance = balance - transactionAmount;
|
||||||
|
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after withdrawal");
|
||||||
|
|
||||||
|
float holdAmount = 50F;
|
||||||
|
Integer holdTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, String.valueOf(holdAmount),
|
||||||
|
false, SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
Assertions.assertNotNull(holdTransactionId);
|
||||||
|
balance = balance - holdAmount;
|
||||||
|
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after hold amount");
|
||||||
|
this.savingsAccountHelper.releaseAmount(savingsId, holdTransactionId);
|
||||||
|
balance = balance + holdAmount;
|
||||||
|
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after release amount");
|
||||||
|
|
||||||
|
depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, String.valueOf(transactionAmount),
|
||||||
|
SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
Assertions.assertNotNull(depositTransactionId);
|
||||||
|
balance = balance + transactionAmount;
|
||||||
|
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after hold-release-deposit");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSavingsAccountDepositAfterNegativeHoldAmountNoInterest() {
|
||||||
|
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||||
|
|
||||||
|
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||||
|
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||||
|
|
||||||
|
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, "0", null, false, true, false,
|
||||||
|
BigDecimal.ZERO);
|
||||||
|
Assertions.assertNotNull(savingsProductID);
|
||||||
|
|
||||||
|
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
|
||||||
|
this.savingsAccountHelper.approveSavings(savingsId);
|
||||||
|
HashMap savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
|
||||||
|
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
|
||||||
|
|
||||||
|
float balance = 0F;
|
||||||
|
float transactionAmount = 100F;
|
||||||
|
Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId,
|
||||||
|
String.valueOf(transactionAmount), SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
Assertions.assertNotNull(depositTransactionId);
|
||||||
|
balance = balance + transactionAmount;
|
||||||
|
HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after deposit");
|
||||||
|
HashMap depositTransaction = savingsAccountHelper.getTransactionDetails(savingsId, depositTransactionId);
|
||||||
|
assertEquals(balance, depositTransaction.get("runningBalance"), "Verifying Running Balance of deposit");
|
||||||
|
Integer withdrawalTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId,
|
||||||
|
String.valueOf(transactionAmount), SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
Assertions.assertNotNull(withdrawalTransactionId);
|
||||||
|
balance = balance - transactionAmount;
|
||||||
|
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after withdrawal");
|
||||||
|
HashMap withdrawalTransaction = savingsAccountHelper.getTransactionDetails(savingsId, withdrawalTransactionId);
|
||||||
|
assertEquals(balance, withdrawalTransaction.get("runningBalance"), "Verifying Running Balance of withdraw");
|
||||||
|
|
||||||
|
float holdAmount = 50F;
|
||||||
|
Integer holdTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, String.valueOf(holdAmount),
|
||||||
|
false, SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
Assertions.assertNotNull(holdTransactionId);
|
||||||
|
balance = balance - holdAmount;
|
||||||
|
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after hold amount");
|
||||||
|
Integer releaseTransactionId = this.savingsAccountHelper.releaseAmount(savingsId, holdTransactionId);
|
||||||
|
Assertions.assertNotNull(releaseTransactionId);
|
||||||
|
balance = balance + holdAmount;
|
||||||
|
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after release amount");
|
||||||
|
|
||||||
|
depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, String.valueOf(transactionAmount),
|
||||||
|
SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
Assertions.assertNotNull(depositTransactionId);
|
||||||
|
balance = balance + transactionAmount;
|
||||||
|
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after hold-release-deposit");
|
||||||
|
depositTransaction = savingsAccountHelper.getTransactionDetails(savingsId, depositTransactionId);
|
||||||
|
// this is a backdated transaction and so listed before the release transaction
|
||||||
|
assertEquals(balance - holdAmount, depositTransaction.get("runningBalance"),
|
||||||
|
"Verifying Running Balance of deposit negative balance");
|
||||||
|
|
||||||
|
HashMap releaseTransaction = savingsAccountHelper.getTransactionDetails(savingsId, releaseTransactionId);
|
||||||
|
assertFalse((Boolean) releaseTransaction.get("reversed"), "Verifying release transaction with overdraft is not reversed");
|
||||||
|
assertEquals(balance, releaseTransaction.get("runningBalance"), "Verifying Running Balance");
|
||||||
|
}
|
||||||
|
|
||||||
|
// LienAtProductLevel
|
||||||
|
private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||||
|
final String minOpenningBalance, String minBalanceForInterestCalculation, final boolean enforceMinRequiredBalance,
|
||||||
|
final boolean allowOverDraft, final boolean lienAllowed, BigDecimal interestRate) {
|
||||||
|
|
||||||
|
LOG.info("------------------------------CREATING NEW SAVINGS PRODUCT WITH LIEN---------------------------------------");
|
||||||
|
SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
|
||||||
|
if (lienAllowed) {
|
||||||
|
final String maxAllowedLienLimit = "2000.0";
|
||||||
|
savingsProductHelper.withLienAllowed(maxAllowedLienLimit);
|
||||||
|
}
|
||||||
|
if (enforceMinRequiredBalance) {
|
||||||
|
final String minRequiredBalance = "100.0";
|
||||||
|
savingsProductHelper.withMinRequiredBalance(minRequiredBalance);
|
||||||
|
savingsProductHelper.withEnforceMinRequiredBalance("true");
|
||||||
|
}
|
||||||
|
if (allowOverDraft) {
|
||||||
|
final String overDraftLimit = "500.0";
|
||||||
|
savingsProductHelper.withOverDraft(overDraftLimit);
|
||||||
|
}
|
||||||
|
if (interestRate != null) {
|
||||||
|
savingsProductHelper.withNominalAnnualInterestRate(interestRate);
|
||||||
|
}
|
||||||
|
final String savingsProductJSON = savingsProductHelper.withInterestCompoundingPeriodTypeAsDaily()
|
||||||
|
.withInterestPostingPeriodTypeAsMonthly().withInterestCalculationPeriodTypeAsDailyBalance()
|
||||||
|
.withMinBalanceForInterestCalculation(minBalanceForInterestCalculation).withMinimumOpenningBalance(minOpenningBalance)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
GlobalConfigurationHelper.resetAllDefaultGlobalConfigurations(this.requestSpec, this.responseSpec);
|
||||||
|
GlobalConfigurationHelper.verifyAllDefaultGlobalConfigurations(this.requestSpec, this.responseSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
-11
@@ -122,30 +122,35 @@ public class SavingsAccountHelper extends IntegrationTest {
|
|||||||
return sdf.format(calendar.getTime());
|
return sdf.format(calendar.getTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer applyForSavingsApplication(final Integer id, final Integer savingsProductID, final String accountType) {
|
public Integer applyForSavingsApplication(final Integer clientOrGroupId, final Integer savingsProductID, final String accountType) {
|
||||||
return applyForSavingsApplicationOnDate(id, savingsProductID, accountType, CREATED_DATE);
|
return applyForSavingsApplicationOnDate(clientOrGroupId, savingsProductID, accountType, CREATED_DATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer applyForSavingsApplicationOnDate(final Integer id, final Integer savingsProductID, final String accountType,
|
public Integer applyForSavingsApplicationOnDate(final Integer clientOrGroupId, final Integer savingsProductID, final String accountType,
|
||||||
final String submittedOnDate) {
|
final String submittedOnDate) {
|
||||||
return applyForSavingsApplicationOnDate(id, savingsProductID, accountType, null, false, submittedOnDate);
|
return applyForSavingsApplicationOnDate(clientOrGroupId, savingsProductID, accountType, null, false, submittedOnDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer applyForSavingsApplicationWithExternalId(final Integer id, final Integer savingsProductID, final String accountType,
|
public Integer applyForSavingsApplicationWithExternalId(final Integer clientOrGroupId, final Integer savingsProductID,
|
||||||
String externalId, boolean withdrawalFeeForTransfers) {
|
final String accountType, String externalId, boolean withdrawalFeeForTransfers) {
|
||||||
return applyForSavingsApplicationOnDate(id, savingsProductID, accountType, externalId, withdrawalFeeForTransfers, CREATED_DATE);
|
return applyForSavingsApplicationOnDate(clientOrGroupId, savingsProductID, accountType, externalId, withdrawalFeeForTransfers,
|
||||||
|
CREATED_DATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer applyForSavingsApplicationOnDate(final Integer id, final Integer savingsProductID, final String accountType,
|
public Integer applyForSavingsApplicationOnDate(final Integer clientOrGroupId, final Integer savingsProductID, final String accountType,
|
||||||
String externalId, boolean withdrawalFeeForTransfers, final String submittedOnDate) {
|
String externalId, boolean withdrawalFeeForTransfers, final String submittedOnDate) {
|
||||||
LOG.info("--------------------------------APPLYING FOR SAVINGS APPLICATION--------------------------------");
|
|
||||||
final String savingsApplicationJSON = new SavingsApplicationTestBuilder() //
|
final String savingsApplicationJSON = new SavingsApplicationTestBuilder() //
|
||||||
.withExternalId(externalId) //
|
.withExternalId(externalId) //
|
||||||
.withWithdrawalFeeForTransfers(withdrawalFeeForTransfers) //
|
.withWithdrawalFeeForTransfers(withdrawalFeeForTransfers) //
|
||||||
.withSubmittedOnDate(submittedOnDate) //
|
.withSubmittedOnDate(submittedOnDate) //
|
||||||
.build(id.toString(), savingsProductID.toString(), accountType);
|
.build(clientOrGroupId.toString(), savingsProductID.toString(), accountType);
|
||||||
|
return applyForSavingsApplicationOnDate(savingsApplicationJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer applyForSavingsApplicationOnDate(String savingsApplicationJson) {
|
||||||
|
LOG.info("--------------------------------APPLYING FOR SAVINGS APPLICATION--------------------------------");
|
||||||
return Utils.performServerPost(this.requestSpec, this.responseSpec, SAVINGS_ACCOUNT_URL + "?" + Utils.TENANT_IDENTIFIER,
|
return Utils.performServerPost(this.requestSpec, this.responseSpec, SAVINGS_ACCOUNT_URL + "?" + Utils.TENANT_IDENTIFIER,
|
||||||
savingsApplicationJSON, "savingsId");
|
savingsApplicationJson, "savingsId");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer applyForSavingsApplicationWithDatatables(final Integer id, final Integer savingsProductID, final String accountType,
|
public Integer applyForSavingsApplicationWithDatatables(final Integer id, final Integer savingsProductID, final String accountType,
|
||||||
@@ -923,4 +928,11 @@ public class SavingsAccountHelper extends IntegrationTest {
|
|||||||
updateGsimJSON(clientID.toString(), groupID.toString(), productID.toString()), "");
|
updateGsimJSON(clientID.toString(), groupID.toString(), productID.toString()), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashMap getTransactionDetails(Integer savingsId, Integer transactionId) {
|
||||||
|
LOG.info("--------------------------------- GET savings transaction details -------------------------------");
|
||||||
|
final String url = "/fineract-provider/api/v1/savingsaccounts/" + savingsId + "/transactions/" + transactionId + "?"
|
||||||
|
+ Utils.TENANT_IDENTIFIER;
|
||||||
|
return Utils.performServerGet(requestSpec, responseSpec, url, "");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -43,14 +43,14 @@ public class SavingsApplicationTestBuilder {
|
|||||||
private List<Map<String, Object>> clientArray = null;
|
private List<Map<String, Object>> clientArray = null;
|
||||||
private List<Map<String, Object>> savingsArray = null;
|
private List<Map<String, Object>> savingsArray = null;
|
||||||
|
|
||||||
public String build(final String id, final String savingsProductId, final String accountType) {
|
public String build(final String clientOrGroupId, final String savingsProductId, final String accountType) {
|
||||||
|
|
||||||
final HashMap<String, Object> map = new HashMap<>();
|
final HashMap<String, Object> map = new HashMap<>();
|
||||||
map.put("dateFormat", "dd MMMM yyyy");
|
map.put("dateFormat", "dd MMMM yyyy");
|
||||||
if (accountType.equals("GROUP")) {
|
if (accountType.equals("GROUP")) {
|
||||||
map.put("groupId", id);
|
map.put("groupId", clientOrGroupId);
|
||||||
} else {
|
} else {
|
||||||
map.put("clientId", id);
|
map.put("clientId", clientOrGroupId);
|
||||||
}
|
}
|
||||||
map.put("productId", savingsProductId);
|
map.put("productId", savingsProductId);
|
||||||
map.put("locale", LOCALE);
|
map.put("locale", LOCALE);
|
||||||
|
|||||||
Reference in New Issue
Block a user