FINERACT-2011: Savings Account - transaction order is incorrectly mixed after certain activities
This commit is contained in:
+1
-4
@@ -64,10 +64,9 @@ public class GlobalConfigurationProperty extends AbstractPersistableCustom {
|
||||
private boolean isTrapDoor;
|
||||
|
||||
public Map<String, Object> update(final JsonCommand command) {
|
||||
|
||||
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
|
||||
|
||||
if (this.isTrapDoor == true) {
|
||||
if (this.isTrapDoor) {
|
||||
throw new GlobalConfigurationPropertyCannotBeModfied(this.getId());
|
||||
}
|
||||
|
||||
@@ -109,7 +108,6 @@ public class GlobalConfigurationProperty extends AbstractPersistableCustom {
|
||||
}
|
||||
|
||||
return actualChanges;
|
||||
|
||||
}
|
||||
|
||||
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())
|
||||
.setDateValue(getDateValue()).setStringValue(getStringValue()).setId(this.getId()).setDescription(this.description)
|
||||
.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.service.DateUtils;
|
||||
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.interoperation.domain.InteropIdentifier;
|
||||
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,
|
||||
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill,
|
||||
postReversals);
|
||||
if (postingPeriods.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Money interestPostedToDate = Money.zero(this.currency);
|
||||
|
||||
@@ -811,109 +815,103 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
|
||||
final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final boolean postReversals) {
|
||||
|
||||
// no openingBalance concept supported yet but probably will to allow
|
||||
// for migrations.
|
||||
Money openingAccountBalance = null;
|
||||
|
||||
// no openingBalance concept supported yet but probably will to allow for migrations.
|
||||
// Check global configurations and 'pivot' date is null
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
openingAccountBalance = Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate());
|
||||
} else {
|
||||
openingAccountBalance = Money.zero(this.currency);
|
||||
}
|
||||
|
||||
// update existing transactions so derived balance fields are
|
||||
// correct.
|
||||
Money openingAccountBalance = backdatedTxnsAllowedTill ? Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate())
|
||||
: Money.zero(this.currency);
|
||||
|
||||
// update existing transactions so derived balance fields are correct.
|
||||
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<>();
|
||||
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;
|
||||
if (this.startInterestCalculationDate != null && !this.getStartInterestCalculationDate().equals(this.getActivationDate())) {
|
||||
LocalDate startInterestCalculationDate = this.startInterestCalculationDate;
|
||||
SavingsAccountTransaction transaction = null;
|
||||
// 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) {
|
||||
transaction = findLastFilteredTransactionWithPivotConfig(startInterestCalculationDate);
|
||||
postedAsOnDates = getManualPostingDatesWithPivotConfig();
|
||||
} 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);
|
||||
} 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) {
|
||||
this.summary.updateSummaryWithPivotConfig(this.currency, this.savingsAccountTransactionSummaryWrapper, null,
|
||||
this.savingsAccountTransactions);
|
||||
@@ -933,6 +931,14 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
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() {
|
||||
final List<SavingsAccountTransaction> listOfTransactionsSorted = retrieveListOfTransactions();
|
||||
|
||||
@@ -982,7 +988,8 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
|
||||
protected void recalculateDailyBalances(final Money openingAccountBalance, final LocalDate interestPostingUpToDate,
|
||||
final boolean backdatedTxnsAllowedTill, boolean postReversals) {
|
||||
Money runningBalance = openingAccountBalance.copy();
|
||||
Money runningBalance = openingAccountBalance;
|
||||
boolean calculateInterest = hasInterestCalculation() || hasOverdraftInterestCalculation();
|
||||
|
||||
List<SavingsAccountTransaction> accountTransactionsSorted = null;
|
||||
|
||||
@@ -1017,15 +1024,14 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
|
||||
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()) {
|
||||
overdraftAmount = overdraftAmount.plus(runningBalance.getAmount().negate());
|
||||
}
|
||||
if (transaction.getId() == null && overdraftAmount.isGreaterThanZero()) {
|
||||
if (!calculateInterest || transaction.getId() == null) {
|
||||
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);
|
||||
if (transaction.isChargeTransaction()) {
|
||||
Set<SavingsAccountChargePaidBy> chargesPaidBy = transaction.getSavingsAccountChargesPaid();
|
||||
@@ -1039,7 +1045,7 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
if (postReversals) {
|
||||
reversal = SavingsAccountTransaction.reversal(transaction);
|
||||
}
|
||||
if (overdraftAmount.isGreaterThanZero()) {
|
||||
if (MathUtil.isGreaterThanZero(overdraftAmount)) {
|
||||
accountTransaction.setOverdraftAmount(overdraftAmount);
|
||||
}
|
||||
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.PlatformServiceUnavailableException;
|
||||
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.StatusEnum;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
|
||||
@@ -579,8 +580,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||
|
||||
if (savingsAccountData.getNominalAnnualInterestRate().compareTo(BigDecimal.ZERO) > 0 || (savingsAccountData.isAllowOverdraft()
|
||||
&& savingsAccountData.getNominalAnnualInterestRateOverdraft().compareTo(BigDecimal.ZERO) > 0)) {
|
||||
if (MathUtil.isGreaterThanZero(savingsAccountData.getNominalAnnualInterestRate()) || (savingsAccountData.isAllowOverdraft()
|
||||
&& MathUtil.isGreaterThanZero(savingsAccountData.getNominalAnnualInterestRateOverdraft()))) {
|
||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||
updateExistingTransactionsDetails(savingsAccountData, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
Reference in New Issue
Block a user