FINERACT-2312: Post interest with adjustments for savings accounts
This commit is contained in:
committed by
Adam Saghy
parent
ef1e2e1095
commit
a6fa1adc99
+10
@@ -29,6 +29,7 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
@@ -47,6 +48,7 @@ import org.apache.fineract.portfolio.tax.data.TaxGroupData;
|
|||||||
/**
|
/**
|
||||||
* Immutable data object representing a savings account.
|
* Immutable data object representing a savings account.
|
||||||
*/
|
*/
|
||||||
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
@JsonLocalDateArrayFormat
|
@JsonLocalDateArrayFormat
|
||||||
public final class SavingsAccountData implements Serializable {
|
public final class SavingsAccountData implements Serializable {
|
||||||
@@ -144,6 +146,13 @@ public final class SavingsAccountData implements Serializable {
|
|||||||
private transient Long glAccountIdForSavingsControl;
|
private transient Long glAccountIdForSavingsControl;
|
||||||
private transient Long glAccountIdForInterestOnSavings;
|
private transient Long glAccountIdForInterestOnSavings;
|
||||||
|
|
||||||
|
private Long glAccountIdForInterestPayable;
|
||||||
|
private Long glAccountIdForOverdraftPorfolio;
|
||||||
|
private Long glAccountIdForInterestReceivable;
|
||||||
|
|
||||||
|
private BigDecimal interestPosting;
|
||||||
|
private BigDecimal overdraftPosting;
|
||||||
|
|
||||||
public static SavingsAccountData importInstanceIndividual(Long clientId, Long productId, Long fieldOfficerId, LocalDate submittedOnDate,
|
public static SavingsAccountData importInstanceIndividual(Long clientId, Long productId, Long fieldOfficerId, LocalDate submittedOnDate,
|
||||||
BigDecimal nominalAnnualInterestRate, EnumOptionData interestCompoundingPeriodTypeEnum,
|
BigDecimal nominalAnnualInterestRate, EnumOptionData interestCompoundingPeriodTypeEnum,
|
||||||
EnumOptionData interestPostingPeriodTypeEnum, EnumOptionData interestCalculationTypeEnum,
|
EnumOptionData interestPostingPeriodTypeEnum, EnumOptionData interestCalculationTypeEnum,
|
||||||
@@ -964,4 +973,5 @@ public final class SavingsAccountData implements Serializable {
|
|||||||
public boolean isIsDormancyTrackingActive() {
|
public boolean isIsDormancyTrackingActive() {
|
||||||
return this.isDormancyTrackingActive;
|
return this.isDormancyTrackingActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-17
@@ -105,6 +105,10 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
private BigDecimal overdraftAmount;
|
private BigDecimal overdraftAmount;
|
||||||
private transient Long modifiedId;
|
private transient Long modifiedId;
|
||||||
private transient String refNo;
|
private transient String refNo;
|
||||||
|
private Boolean isOverdraft;
|
||||||
|
|
||||||
|
private Long accountCredit;
|
||||||
|
private Long accountDebit;
|
||||||
|
|
||||||
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
||||||
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate transactionDate,
|
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate transactionDate,
|
||||||
@@ -112,7 +116,7 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
final boolean reversed, final AccountTransferData transfer, final Collection<PaymentTypeData> paymentTypeOptions,
|
final boolean reversed, final AccountTransferData transfer, final Collection<PaymentTypeData> paymentTypeOptions,
|
||||||
final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final String submittedByUsername, final String note,
|
final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final String submittedByUsername, final String note,
|
||||||
final Boolean isReversal, final Long originalTransactionId, boolean isManualTransaction, final Boolean lienTransaction,
|
final Boolean isReversal, final Long originalTransactionId, boolean isManualTransaction, final Boolean lienTransaction,
|
||||||
final Long releaseTransactionId, final String reasonForBlock) {
|
final Long releaseTransactionId, final String reasonForBlock, final Boolean isOverdraft) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.transactionType = transactionType;
|
this.transactionType = transactionType;
|
||||||
TransactionEntryType entryType = null;
|
TransactionEntryType entryType = null;
|
||||||
@@ -146,6 +150,7 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
this.lienTransaction = lienTransaction;
|
this.lienTransaction = lienTransaction;
|
||||||
this.releaseTransactionId = releaseTransactionId;
|
this.releaseTransactionId = releaseTransactionId;
|
||||||
this.reasonForBlock = reasonForBlock;
|
this.reasonForBlock = reasonForBlock;
|
||||||
|
this.isOverdraft = isOverdraft;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SavingsAccountTransactionData createData(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
private static SavingsAccountTransactionData createData(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
||||||
@@ -156,7 +161,7 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
final Boolean lienTransaction) {
|
final Boolean lienTransaction) {
|
||||||
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, accountId, accountNo, date, currency, amount,
|
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, accountId, accountNo, date, currency, amount,
|
||||||
outstandingChargeAmount, runningBalance, reversed, transfer, paymentTypeOptions, submittedOnDate, interestedPostedAsOn,
|
outstandingChargeAmount, runningBalance, reversed, transfer, paymentTypeOptions, submittedOnDate, interestedPostedAsOn,
|
||||||
submittedByUsername, note, null, null, false, lienTransaction, null, null);
|
submittedByUsername, note, null, null, false, lienTransaction, null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
||||||
@@ -167,7 +172,8 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
final Boolean lienTransaction, final Long releaseTransactionId, final String reasonForBlock) {
|
final Boolean lienTransaction, final Long releaseTransactionId, final String reasonForBlock) {
|
||||||
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
|
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
|
||||||
amount, outstandingChargeAmount, runningBalance, reversed, transfer, null, submittedOnDate, interestedPostedAsOn,
|
amount, outstandingChargeAmount, runningBalance, reversed, transfer, null, submittedOnDate, interestedPostedAsOn,
|
||||||
submittedByUsername, note, isReversal, originalTransactionId, false, lienTransaction, releaseTransactionId, reasonForBlock);
|
submittedByUsername, note, isReversal, originalTransactionId, false, lienTransaction, releaseTransactionId, reasonForBlock,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
||||||
@@ -235,10 +241,10 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
private static SavingsAccountTransactionData createImport(final SavingsAccountTransactionEnumData transactionType,
|
private static SavingsAccountTransactionData createImport(final SavingsAccountTransactionEnumData transactionType,
|
||||||
final PaymentDetailData paymentDetailData, final Long savingsAccountId, final String accountNumber,
|
final PaymentDetailData paymentDetailData, final Long savingsAccountId, final String accountNumber,
|
||||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final boolean reversed, final LocalDate submittedOnDate,
|
final LocalDate transactionDate, final BigDecimal transactionAmount, final boolean reversed, final LocalDate submittedOnDate,
|
||||||
boolean isManualTransaction, final Boolean lienTransaction) {
|
boolean isManualTransaction, final Boolean lienTransaction, final Boolean isOverdraft) {
|
||||||
SavingsAccountTransactionData data = new SavingsAccountTransactionData(null, transactionType, paymentDetailData, savingsAccountId,
|
SavingsAccountTransactionData data = new SavingsAccountTransactionData(null, transactionType, paymentDetailData, savingsAccountId,
|
||||||
accountNumber, transactionDate, null, transactionAmount, null, null, reversed, null, null, submittedOnDate, false, null,
|
accountNumber, transactionDate, null, transactionAmount, null, null, reversed, null, null, submittedOnDate, false, null,
|
||||||
null, null, null, isManualTransaction, lienTransaction, null, null);
|
null, null, null, isManualTransaction, lienTransaction, null, null, isOverdraft);
|
||||||
// duplicated import fields
|
// duplicated import fields
|
||||||
data.savingsAccountId = savingsAccountId;
|
data.savingsAccountId = savingsAccountId;
|
||||||
data.accountNumber = accountNumber;
|
data.accountNumber = accountNumber;
|
||||||
@@ -251,14 +257,14 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
return createImport(accountTransaction.getTransactionType(), accountTransaction.getPaymentDetailData(),
|
return createImport(accountTransaction.getTransactionType(), accountTransaction.getPaymentDetailData(),
|
||||||
accountTransaction.getSavingsAccountId(), null, accountTransaction.getTransactionDate(), accountTransaction.getAmount(),
|
accountTransaction.getSavingsAccountId(), null, accountTransaction.getTransactionDate(), accountTransaction.getAmount(),
|
||||||
accountTransaction.isReversed(), accountTransaction.getSubmittedOnDate(), accountTransaction.isManualTransaction(),
|
accountTransaction.isReversed(), accountTransaction.getSubmittedOnDate(), accountTransaction.isManualTransaction(),
|
||||||
accountTransaction.getLienTransaction());
|
accountTransaction.getLienTransaction(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SavingsAccountTransactionData importInstance(BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId,
|
public static SavingsAccountTransactionData importInstance(BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId,
|
||||||
String accountNumber, String checkNumber, String routingCode, String receiptNumber, String bankNumber, String note,
|
String accountNumber, String checkNumber, String routingCode, String receiptNumber, String bankNumber, String note,
|
||||||
Long savingsAccountId, SavingsAccountTransactionEnumData transactionType, Integer rowIndex, String locale, String dateFormat) {
|
Long savingsAccountId, SavingsAccountTransactionEnumData transactionType, Integer rowIndex, String locale, String dateFormat) {
|
||||||
SavingsAccountTransactionData data = createImport(transactionType, null, savingsAccountId, accountNumber, transactionDate,
|
SavingsAccountTransactionData data = createImport(transactionType, null, savingsAccountId, accountNumber, transactionDate,
|
||||||
transactionAmount, false, transactionDate, false, false);
|
transactionAmount, false, transactionDate, false, false, false);
|
||||||
data.rowIndex = rowIndex;
|
data.rowIndex = rowIndex;
|
||||||
data.paymentTypeId = paymentTypeId;
|
data.paymentTypeId = paymentTypeId;
|
||||||
data.checkNumber = checkNumber;
|
data.checkNumber = checkNumber;
|
||||||
@@ -272,10 +278,11 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static SavingsAccountTransactionData createImport(SavingsAccountTransactionEnumData transactionType, Long savingsAccountId,
|
private static SavingsAccountTransactionData createImport(SavingsAccountTransactionEnumData transactionType, Long savingsAccountId,
|
||||||
LocalDate transactionDate, BigDecimal transactionAmount, final LocalDate submittedOnDate, boolean isManualTransaction) {
|
LocalDate transactionDate, BigDecimal transactionAmount, final LocalDate submittedOnDate, boolean isManualTransaction,
|
||||||
|
Boolean isOverdraft) {
|
||||||
// import transaction
|
// import transaction
|
||||||
return createImport(transactionType, null, savingsAccountId, null, transactionDate, transactionAmount, false, submittedOnDate,
|
return createImport(transactionType, null, savingsAccountId, null, transactionDate, transactionAmount, false, submittedOnDate,
|
||||||
isManualTransaction, false);
|
isManualTransaction, false, isOverdraft);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SavingsAccountTransactionData interestPosting(final SavingsAccountData savingsAccount, final LocalDate date,
|
public static SavingsAccountTransactionData interestPosting(final SavingsAccountData savingsAccount, final LocalDate date,
|
||||||
@@ -285,17 +292,28 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
SavingsAccountTransactionEnumData transactionType = new SavingsAccountTransactionEnumData(
|
SavingsAccountTransactionEnumData transactionType = new SavingsAccountTransactionEnumData(
|
||||||
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
|
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
|
||||||
savingsAccountTransactionType.getValue().toString());
|
savingsAccountTransactionType.getValue().toString());
|
||||||
return createImport(transactionType, savingsAccount.getId(), date, amount.getAmount(), submittedOnDate, isManualTransaction);
|
return createImport(transactionType, savingsAccount.getId(), date, amount.getAmount(), submittedOnDate, isManualTransaction, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SavingsAccountTransactionData accrual(final SavingsAccountData savingsAccount, final LocalDate date, final Money amount,
|
||||||
|
final boolean isManualTransaction) {
|
||||||
|
final LocalDate submittedOnDate = DateUtils.getBusinessLocalDate();
|
||||||
|
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.ACCRUAL;
|
||||||
|
SavingsAccountTransactionEnumData transactionType = new SavingsAccountTransactionEnumData(
|
||||||
|
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
|
||||||
|
savingsAccountTransactionType.getValue().toString());
|
||||||
|
return createImport(transactionType, savingsAccount.getId(), date, amount.getAmount(), submittedOnDate, isManualTransaction, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SavingsAccountTransactionData overdraftInterest(final SavingsAccountData savingsAccount, final LocalDate date,
|
public static SavingsAccountTransactionData overdraftInterest(final SavingsAccountData savingsAccount, final LocalDate date,
|
||||||
final Money amount, final boolean isManualTransaction) {
|
final Money amount, final boolean isManualTransaction, final Boolean isOverdraft) {
|
||||||
final LocalDate submittedOnDate = DateUtils.getBusinessLocalDate();
|
final LocalDate submittedOnDate = DateUtils.getBusinessLocalDate();
|
||||||
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.OVERDRAFT_INTEREST;
|
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.OVERDRAFT_INTEREST;
|
||||||
SavingsAccountTransactionEnumData transactionType = new SavingsAccountTransactionEnumData(
|
SavingsAccountTransactionEnumData transactionType = new SavingsAccountTransactionEnumData(
|
||||||
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
|
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
|
||||||
savingsAccountTransactionType.getValue().toString());
|
savingsAccountTransactionType.getValue().toString());
|
||||||
return createImport(transactionType, savingsAccount.getId(), date, amount.getAmount(), submittedOnDate, isManualTransaction);
|
return createImport(transactionType, savingsAccount.getId(), date, amount.getAmount(), submittedOnDate, isManualTransaction,
|
||||||
|
isOverdraft);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SavingsAccountTransactionData withHoldTax(final SavingsAccountData savingsAccount, final LocalDate date,
|
public static SavingsAccountTransactionData withHoldTax(final SavingsAccountData savingsAccount, final LocalDate date,
|
||||||
@@ -306,7 +324,7 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
|
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
|
||||||
savingsAccountTransactionType.getValue().toString());
|
savingsAccountTransactionType.getValue().toString());
|
||||||
SavingsAccountTransactionData accountTransaction = createImport(transactionType, savingsAccount.getId(), date, amount.getAmount(),
|
SavingsAccountTransactionData accountTransaction = createImport(transactionType, savingsAccount.getId(), date, amount.getAmount(),
|
||||||
submittedOnDate, false);
|
submittedOnDate, false, false);
|
||||||
accountTransaction.addTaxDetails(taxDetails);
|
accountTransaction.addTaxDetails(taxDetails);
|
||||||
return accountTransaction;
|
return accountTransaction;
|
||||||
}
|
}
|
||||||
@@ -387,10 +405,11 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
final MonetaryCurrency currency = openingBalance.getCurrency();
|
final MonetaryCurrency currency = openingBalance.getCurrency();
|
||||||
Money endOfDayBalance = openingBalance.copy();
|
Money endOfDayBalance = openingBalance.copy();
|
||||||
if (isDeposit() || isDividendPayoutAndNotReversed()) {
|
if (isDeposit() || isDividendPayoutAndNotReversed()) {
|
||||||
endOfDayBalance = openingBalance.plus(getAmount());
|
endOfDayBalance = Money.of(currency, this.runningBalance);
|
||||||
} else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
|
} else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
|
||||||
|
if (isWithdrawal()) {
|
||||||
if (openingBalance.isGreaterThanZero()) {
|
endOfDayBalance = Money.of(currency, this.runningBalance);
|
||||||
|
} else if (openingBalance.isGreaterThanZero()) {
|
||||||
endOfDayBalance = openingBalance.minus(getAmount());
|
endOfDayBalance = openingBalance.minus(getAmount());
|
||||||
} else {
|
} else {
|
||||||
endOfDayBalance = Money.of(currency, this.runningBalance);
|
endOfDayBalance = Money.of(currency, this.runningBalance);
|
||||||
@@ -400,6 +419,14 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
return EndOfDayBalance.from(getTransactionDate(), openingBalance, endOfDayBalance, this.balanceNumberOfDays);
|
return EndOfDayBalance.from(getTransactionDate(), openingBalance, endOfDayBalance, this.balanceNumberOfDays);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EndOfDayBalance toEndOfDayBalanceDates(final Money openingBalance, LocalDateInterval date) {
|
||||||
|
final MonetaryCurrency currency = openingBalance.getCurrency();
|
||||||
|
Money endOfDayBalance = Money.of(currency, this.runningBalance);
|
||||||
|
|
||||||
|
return EndOfDayBalance.from(getTransactionDate(), openingBalance, endOfDayBalance,
|
||||||
|
this.balanceNumberOfDays != null ? this.balanceNumberOfDays : date.endDate().getDayOfMonth());
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isChargeTransactionAndNotReversed() {
|
public boolean isChargeTransactionAndNotReversed() {
|
||||||
return this.transactionType.isChargeTransaction() && isNotReversed();
|
return this.transactionType.isChargeTransaction() && isNotReversed();
|
||||||
}
|
}
|
||||||
@@ -427,7 +454,9 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
if (isDeposit() || isDividendPayoutAndNotReversed()) {
|
if (isDeposit() || isDividendPayoutAndNotReversed()) {
|
||||||
endOfDayBalance = endOfDayBalance.plus(getAmount());
|
endOfDayBalance = endOfDayBalance.plus(getAmount());
|
||||||
} else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
|
} else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
|
||||||
if (endOfDayBalance.isGreaterThanZero() || isAllowOverdraft) {
|
if (endOfDayBalance.isLessThanZero() && isAllowOverdraft) {
|
||||||
|
endOfDayBalance = Money.of(currency, this.runningBalance);
|
||||||
|
} else if (endOfDayBalance.isGreaterThanZero() || isAllowOverdraft) {
|
||||||
endOfDayBalance = endOfDayBalance.minus(getAmount());
|
endOfDayBalance = endOfDayBalance.minus(getAmount());
|
||||||
} else {
|
} else {
|
||||||
endOfDayBalance = Money.of(currency, this.runningBalance);
|
endOfDayBalance = Money.of(currency, this.runningBalance);
|
||||||
@@ -658,4 +687,12 @@ public final class SavingsAccountTransactionData implements Serializable {
|
|||||||
public TransactionEntryType getEntryType() {
|
public TransactionEntryType getEntryType() {
|
||||||
return entryType;
|
return entryType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAccountCredit(Long accountCredit) {
|
||||||
|
this.accountCredit = accountCredit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccountDebit(Long accountDebit) {
|
||||||
|
this.accountDebit = accountDebit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -47,9 +47,14 @@ public class CompoundInterestHelper {
|
|||||||
// total interest earned in previous periods but not yet recognised
|
// total interest earned in previous periods but not yet recognised
|
||||||
BigDecimal compoundedInterest = BigDecimal.ZERO;
|
BigDecimal compoundedInterest = BigDecimal.ZERO;
|
||||||
BigDecimal unCompoundedInterest = BigDecimal.ZERO;
|
BigDecimal unCompoundedInterest = BigDecimal.ZERO;
|
||||||
|
LocalDate endDay = DateUtils.getBusinessLocalDate();
|
||||||
final CompoundInterestValues compoundInterestValues = new CompoundInterestValues(compoundedInterest, unCompoundedInterest);
|
final CompoundInterestValues compoundInterestValues = new CompoundInterestValues(compoundedInterest, unCompoundedInterest);
|
||||||
for (final PostingPeriod postingPeriod : allPeriods) {
|
for (final PostingPeriod postingPeriod : allPeriods) {
|
||||||
|
|
||||||
|
if (postingPeriod.dateOfPostingTransaction().getMonth() != endDay.getMonth()) {
|
||||||
|
compoundInterestValues.setCompoundedInterest(interestEarned.getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
final BigDecimal interestEarnedThisPeriod = postingPeriod.calculateInterest(compoundInterestValues);
|
final BigDecimal interestEarnedThisPeriod = postingPeriod.calculateInterest(compoundInterestValues);
|
||||||
|
|
||||||
final Money moneyToBePostedForPeriod = Money.of(currency, interestEarnedThisPeriod);
|
final Money moneyToBePostedForPeriod = Money.of(currency, interestEarnedThisPeriod);
|
||||||
@@ -61,8 +66,9 @@ public class CompoundInterestHelper {
|
|||||||
// calculation.
|
// calculation.
|
||||||
if (!(postingPeriod.isInterestTransfered() || !interestTransferEnabled
|
if (!(postingPeriod.isInterestTransfered() || !interestTransferEnabled
|
||||||
|| (lockUntil != null && !DateUtils.isAfter(postingPeriod.dateOfPostingTransaction(), lockUntil)))) {
|
|| (lockUntil != null && !DateUtils.isAfter(postingPeriod.dateOfPostingTransaction(), lockUntil)))) {
|
||||||
compoundInterestValues.setcompoundedInterest(BigDecimal.ZERO);
|
compoundInterestValues.setCompoundedInterest(BigDecimal.ZERO);
|
||||||
}
|
}
|
||||||
|
endDay = postingPeriod.dateOfPostingTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
return interestEarned;
|
return interestEarned;
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ public class CompoundInterestValues {
|
|||||||
return this.uncompoundedInterest;
|
return this.uncompoundedInterest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setcompoundedInterest(BigDecimal interestToBeCompounded) {
|
public void setCompoundedInterest(BigDecimal interestToBeCompounded) {
|
||||||
this.compoundedInterest = interestToBeCompounded;
|
this.compoundedInterest = interestToBeCompounded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -26,6 +26,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
@@ -34,6 +36,8 @@ import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodTyp
|
|||||||
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
||||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
public final class PostingPeriod {
|
public final class PostingPeriod {
|
||||||
|
|
||||||
private final LocalDateInterval periodInterval;
|
private final LocalDateInterval periodInterval;
|
||||||
@@ -64,6 +68,8 @@ public final class PostingPeriod {
|
|||||||
|
|
||||||
private Integer financialYearBeginningMonth;
|
private Integer financialYearBeginningMonth;
|
||||||
|
|
||||||
|
private boolean overdraftInterest = false;
|
||||||
|
|
||||||
public void setOverdraftInterestRateAsFraction(BigDecimal overdraftInterestRateAsFraction) {
|
public void setOverdraftInterestRateAsFraction(BigDecimal overdraftInterestRateAsFraction) {
|
||||||
this.overdraftInterestRateAsFraction = overdraftInterestRateAsFraction;
|
this.overdraftInterestRateAsFraction = overdraftInterestRateAsFraction;
|
||||||
}
|
}
|
||||||
@@ -301,7 +307,7 @@ public final class PostingPeriod {
|
|||||||
|
|
||||||
if (compoundingPeriodEndDate.equals(compoundingPeriod.getPeriodInterval().endDate())) {
|
if (compoundingPeriodEndDate.equals(compoundingPeriod.getPeriodInterval().endDate())) {
|
||||||
BigDecimal interestCompounded = compoundInterestValues.getcompoundedInterest().add(unCompoundedInterest);
|
BigDecimal interestCompounded = compoundInterestValues.getcompoundedInterest().add(unCompoundedInterest);
|
||||||
compoundInterestValues.setcompoundedInterest(interestCompounded);
|
compoundInterestValues.setCompoundedInterest(interestCompounded);
|
||||||
compoundInterestValues.setZeroForInterestToBeUncompounded();
|
compoundInterestValues.setZeroForInterestToBeUncompounded();
|
||||||
}
|
}
|
||||||
interestEarned = interestEarned.add(interestUnrounded);
|
interestEarned = interestEarned.add(interestUnrounded);
|
||||||
@@ -314,7 +320,7 @@ public final class PostingPeriod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Money getInterestEarned() {
|
public Money getInterestEarned() {
|
||||||
return this.interestEarnedRounded;
|
return this.interestEarnedRounded != null ? this.interestEarnedRounded : Money.zero(this.currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<CompoundingPeriod> compoundingPeriodsInPostingPeriod(final LocalDateInterval postingPeriodInterval,
|
private static List<CompoundingPeriod> compoundingPeriodsInPostingPeriod(final LocalDateInterval postingPeriodInterval,
|
||||||
|
|||||||
+131
-12
@@ -32,6 +32,7 @@ import java.util.stream.Collectors;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
||||||
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.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||||
@@ -81,11 +82,17 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI
|
|||||||
for (final PostingPeriod interestPostingPeriod : postingPeriods) {
|
for (final PostingPeriod interestPostingPeriod : postingPeriods) {
|
||||||
final LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction();
|
final LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction();
|
||||||
final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned();
|
final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned();
|
||||||
|
final Boolean isOverdraft = interestPostingPeriod.isOverdraftInterest();
|
||||||
|
|
||||||
if (!DateUtils.isAfter(interestPostingTransactionDate, interestPostingUpToDate)) {
|
if (!DateUtils.isAfter(interestPostingTransactionDate, interestPostingUpToDate)) {
|
||||||
interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod);
|
interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod);
|
||||||
final SavingsAccountTransactionData postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate,
|
SavingsAccountTransactionData postingTransaction = null;
|
||||||
savingsAccountData);
|
if (this.depositAccountType(savingsAccountData).isSavingsDeposit() && savingsAccountData.isAllowOverdraft()) {
|
||||||
|
postingTransaction = findInterestPostingTransactionForInterest(interestPostingTransactionDate, savingsAccountData,
|
||||||
|
isOverdraft);
|
||||||
|
} else {
|
||||||
|
postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate, savingsAccountData);
|
||||||
|
}
|
||||||
|
|
||||||
if (postingTransaction == null) {
|
if (postingTransaction == null) {
|
||||||
SavingsAccountTransactionData newPostingTransaction;
|
SavingsAccountTransactionData newPostingTransaction;
|
||||||
@@ -95,7 +102,7 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI
|
|||||||
} else {
|
} else {
|
||||||
newPostingTransaction = SavingsAccountTransactionData.overdraftInterest(savingsAccountData,
|
newPostingTransaction = SavingsAccountTransactionData.overdraftInterest(savingsAccountData,
|
||||||
interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated(),
|
interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated(),
|
||||||
interestPostingPeriod.isUserPosting());
|
interestPostingPeriod.isUserPosting(), isOverdraft);
|
||||||
}
|
}
|
||||||
|
|
||||||
savingsAccountData.updateTransactions(newPostingTransaction);
|
savingsAccountData.updateTransactions(newPostingTransaction);
|
||||||
@@ -131,7 +138,7 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI
|
|||||||
} else {
|
} else {
|
||||||
newPostingTransaction = SavingsAccountTransactionData.overdraftInterest(savingsAccountData,
|
newPostingTransaction = SavingsAccountTransactionData.overdraftInterest(savingsAccountData,
|
||||||
interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated(),
|
interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated(),
|
||||||
interestPostingPeriod.isUserPosting());
|
interestPostingPeriod.isUserPosting(), isOverdraft);
|
||||||
}
|
}
|
||||||
|
|
||||||
savingsAccountData.updateTransactions(newPostingTransaction);
|
savingsAccountData.updateTransactions(newPostingTransaction);
|
||||||
@@ -190,6 +197,36 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI
|
|||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Money appendPostingPeriodIfAny(final LocalDateInterval periodInterval, Money periodStartingBalance,
|
||||||
|
final List<SavingsAccountTransactionData> txs, final MonetaryCurrency monetaryCurrency,
|
||||||
|
final SavingsCompoundingInterestPeriodType compoundingPeriodType, final SavingsInterestCalculationType interestCalculationType,
|
||||||
|
final BigDecimal interestRateAsFraction, final int daysInYear, final LocalDate upToInterestCalculationDate,
|
||||||
|
final Collection<Long> interestPostTransactions, final boolean isInterestTransfer, final Money minBalanceForInterestCalculation,
|
||||||
|
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final BigDecimal overdraftInterestRateAsFraction,
|
||||||
|
final Money minOverdraftForInterestCalculation, final boolean isUserPosting, final Integer financialYearBeginningMonth,
|
||||||
|
final boolean allowOverdraft, final List<PostingPeriod> allPostingPeriods, Boolean isOverdraftTransacction) {
|
||||||
|
|
||||||
|
if (txs == null || txs.isEmpty()) {
|
||||||
|
return periodStartingBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
final PostingPeriod postingPeriod = PostingPeriod.createFromDTO(periodInterval, periodStartingBalance, txs, monetaryCurrency,
|
||||||
|
compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYear, upToInterestCalculationDate,
|
||||||
|
interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||||
|
overdraftInterestRateAsFraction, minOverdraftForInterestCalculation, isUserPosting, financialYearBeginningMonth,
|
||||||
|
allowOverdraft);
|
||||||
|
|
||||||
|
periodStartingBalance = postingPeriod.closingBalance();
|
||||||
|
postingPeriod.setOverdraftInterest(isOverdraftTransacction);
|
||||||
|
|
||||||
|
if (!(MathUtil.isZero(postingPeriod.getOpeningBalance().getAmount())
|
||||||
|
&& MathUtil.isZero(postingPeriod.closingBalance().getAmount()))) {
|
||||||
|
allPostingPeriods.add(postingPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
return periodStartingBalance;
|
||||||
|
}
|
||||||
|
|
||||||
public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate upToInterestCalculationDate,
|
public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate upToInterestCalculationDate,
|
||||||
boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
|
boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
|
||||||
final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final SavingsAccountData savingsAccountData) {
|
final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final SavingsAccountData savingsAccountData) {
|
||||||
@@ -268,16 +305,40 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI
|
|||||||
if (postedAsOnDates.contains(periodInterval.endDate().plusDays(1))) {
|
if (postedAsOnDates.contains(periodInterval.endDate().plusDays(1))) {
|
||||||
isUserPosting = true;
|
isUserPosting = true;
|
||||||
}
|
}
|
||||||
final PostingPeriod postingPeriod = PostingPeriod.createFromDTO(periodInterval, periodStartingBalance,
|
if (savingsAccountData.isAllowOverdraft() && !MathUtil.isZero(savingsAccountData.getGlAccountIdForInterestReceivable())) {
|
||||||
retreiveOrderedNonInterestPostingTransactions(savingsAccountData), monetaryCurrency, compoundingPeriodType,
|
|
||||||
interestCalculationType, interestRateAsFraction, daysInYearType.getValue(), upToInterestCalculationDate,
|
|
||||||
interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation,
|
|
||||||
isSavingsInterestPostingAtCurrentPeriodEnd, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation,
|
|
||||||
isUserPosting, financialYearBeginningMonth, savingsAccountData.isAllowOverdraft());
|
|
||||||
|
|
||||||
periodStartingBalance = postingPeriod.closingBalance();
|
List<SavingsAccountTransactionData> overdraftTxs = listForOverdraft(savingsAccountData, periodInterval);
|
||||||
|
List<SavingsAccountTransactionData> interestPostingTxs = listForInterestPosting(savingsAccountData, periodInterval,
|
||||||
|
monetaryCurrency);
|
||||||
|
|
||||||
allPostingPeriods.add(postingPeriod);
|
boolean isOverdraftAccountType = isOverdraftAccount(savingsAccountData, periodInterval, monetaryCurrency);
|
||||||
|
|
||||||
|
List<SavingsAccountTransactionData> primaryInterestPublication = isOverdraftAccountType ? overdraftTxs : interestPostingTxs;
|
||||||
|
List<SavingsAccountTransactionData> secondaryInterestPublication = isOverdraftAccountType ? interestPostingTxs
|
||||||
|
: overdraftTxs;
|
||||||
|
|
||||||
|
periodStartingBalance = appendPostingPeriodIfAny(periodInterval, periodStartingBalance, primaryInterestPublication,
|
||||||
|
monetaryCurrency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYearType.getValue(),
|
||||||
|
upToInterestCalculationDate, interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation,
|
||||||
|
isSavingsInterestPostingAtCurrentPeriodEnd, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation,
|
||||||
|
isUserPosting, financialYearBeginningMonth, savingsAccountData.isAllowOverdraft(), allPostingPeriods,
|
||||||
|
isOverdraftAccountType ? true : false);
|
||||||
|
|
||||||
|
periodStartingBalance = appendPostingPeriodIfAny(periodInterval, periodStartingBalance, secondaryInterestPublication,
|
||||||
|
monetaryCurrency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYearType.getValue(),
|
||||||
|
upToInterestCalculationDate, interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation,
|
||||||
|
isSavingsInterestPostingAtCurrentPeriodEnd, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation,
|
||||||
|
isUserPosting, financialYearBeginningMonth, savingsAccountData.isAllowOverdraft(), allPostingPeriods,
|
||||||
|
isOverdraftAccountType ? false : true);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
periodStartingBalance = appendPostingPeriodIfAny(periodInterval, periodStartingBalance,
|
||||||
|
retreiveOrderedNonInterestPostingTransactions(savingsAccountData), monetaryCurrency, compoundingPeriodType,
|
||||||
|
interestCalculationType, interestRateAsFraction, daysInYearType.getValue(), upToInterestCalculationDate,
|
||||||
|
interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation,
|
||||||
|
isSavingsInterestPostingAtCurrentPeriodEnd, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation,
|
||||||
|
isUserPosting, financialYearBeginningMonth, savingsAccountData.isAllowOverdraft(), allPostingPeriods, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.savingsHelper.calculateInterestForAllPostingPeriods(monetaryCurrency, allPostingPeriods,
|
this.savingsHelper.calculateInterestForAllPostingPeriods(monetaryCurrency, allPostingPeriods,
|
||||||
@@ -297,6 +358,52 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI
|
|||||||
return allPostingPeriods;
|
return allPostingPeriods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<SavingsAccountTransactionData> listForOverdraft(final SavingsAccountData savingsAccountData,
|
||||||
|
final LocalDateInterval periodInterval) {
|
||||||
|
List<SavingsAccountTransactionData> overdraftTransactionsInPeriod = new ArrayList<>();
|
||||||
|
for (SavingsAccountTransactionData lists : retreiveOrderedNonInterestPostingTransactions(savingsAccountData)) {
|
||||||
|
if (MathUtil.isLessThanZero(lists.getRunningBalance()) && periodInterval.startDate().getMonth() == lists.getDate().getMonth()) {
|
||||||
|
overdraftTransactionsInPeriod.add(lists);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return overdraftTransactionsInPeriod;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SavingsAccountTransactionData> listForInterestPosting(final SavingsAccountData savingsAccountData,
|
||||||
|
final LocalDateInterval periodInterval, final MonetaryCurrency currency) {
|
||||||
|
|
||||||
|
final List<SavingsAccountTransactionData> nonOverdraftTransactions = new ArrayList<>();
|
||||||
|
|
||||||
|
for (final SavingsAccountTransactionData tx : retreiveOrderedNonInterestPostingTransactions(savingsAccountData)) {
|
||||||
|
if (periodInterval.startDate().getMonth() == tx.getDate().getMonth()) {
|
||||||
|
final Money runningBalance = Money.of(currency, tx.getRunningBalance());
|
||||||
|
|
||||||
|
if (runningBalance.isGreaterThanZero() && !runningBalance.isZero()) {
|
||||||
|
nonOverdraftTransactions.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nonOverdraftTransactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean isOverdraftAccount(final SavingsAccountData savingsAccountData, final LocalDateInterval periodInterval,
|
||||||
|
final MonetaryCurrency currency) {
|
||||||
|
|
||||||
|
for (SavingsAccountTransactionData tx : retreiveOrderedNonInterestPostingTransactions(savingsAccountData)) {
|
||||||
|
if (MathUtil.isLessThanZero(tx.getRunningBalance()) && periodInterval.startDate().getMonth() == tx.getDate().getMonth()) {
|
||||||
|
return true;
|
||||||
|
} else if (periodInterval.startDate().getMonth() == tx.getDate().getMonth()) {
|
||||||
|
final Money runningBalance = Money.of(currency, tx.getRunningBalance());
|
||||||
|
if (!runningBalance.isZero()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private List<SavingsAccountTransactionData> retreiveOrderedNonInterestPostingTransactions(final SavingsAccountData savingsAccountData) {
|
private List<SavingsAccountTransactionData> retreiveOrderedNonInterestPostingTransactions(final SavingsAccountData savingsAccountData) {
|
||||||
final List<SavingsAccountTransactionData> listOfTransactionsSorted = retrieveListOfTransactions(savingsAccountData);
|
final List<SavingsAccountTransactionData> listOfTransactionsSorted = retrieveListOfTransactions(savingsAccountData);
|
||||||
|
|
||||||
@@ -509,6 +616,18 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI
|
|||||||
return postingTransation;
|
return postingTransation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected SavingsAccountTransactionData findInterestPostingTransactionForInterest(final LocalDate postingDate,
|
||||||
|
final SavingsAccountData savingsAccountData, boolean isOverdraft) {
|
||||||
|
SavingsAccountTransactionData postingTransation = null;
|
||||||
|
List<SavingsAccountTransactionData> transactions = savingsAccountData.getSavingsAccountTransactionData();
|
||||||
|
postingTransation = transactions.stream().filter(t -> {
|
||||||
|
Boolean interestSearch = isOverdraft ? t.isOverdraftInterestAndNotReversed() : t.isInterestPostingAndNotReversed();
|
||||||
|
return interestSearch && t.occursOn(postingDate) && !t.isReversalTransaction();
|
||||||
|
}).findFirst().orElse(null);
|
||||||
|
|
||||||
|
return postingTransation;
|
||||||
|
}
|
||||||
|
|
||||||
protected void resetAccountTransactionsEndOfDayBalances(final List<SavingsAccountTransactionData> accountTransactionsSorted,
|
protected void resetAccountTransactionsEndOfDayBalances(final List<SavingsAccountTransactionData> accountTransactionsSorted,
|
||||||
final LocalDate interestPostingUpToDate, final SavingsAccountData savingsAccountData) {
|
final LocalDate interestPostingUpToDate, final SavingsAccountData savingsAccountData) {
|
||||||
// loop over transactions in reverse
|
// loop over transactions in reverse
|
||||||
|
|||||||
+17
@@ -337,6 +337,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
|||||||
"msac.id as chargeId, msac.amount as chargeAmount, msac.charge_time_enum as chargeTimeType, msac.is_penalty as isPenaltyCharge, ");
|
"msac.id as chargeId, msac.amount as chargeAmount, msac.charge_time_enum as chargeTimeType, msac.is_penalty as isPenaltyCharge, ");
|
||||||
sqlBuilder.append("txd.id as taxDetailsId, txd.amount as taxAmount, ");
|
sqlBuilder.append("txd.id as taxDetailsId, txd.amount as taxAmount, ");
|
||||||
sqlBuilder.append("apm.gl_account_id as glAccountIdForInterestOnSavings, apm1.gl_account_id as glAccountIdForSavingsControl, ");
|
sqlBuilder.append("apm.gl_account_id as glAccountIdForInterestOnSavings, apm1.gl_account_id as glAccountIdForSavingsControl, ");
|
||||||
|
sqlBuilder.append(
|
||||||
|
"apm2.gl_account_id as glAccountIdForInterestReceivable,apm3.gl_account_id as glAccountIdForOverdraftPorfolio, ");
|
||||||
|
sqlBuilder.append("apm4.gl_account_id as glAccountIdForInterestPayable, ");
|
||||||
sqlBuilder.append(
|
sqlBuilder.append(
|
||||||
"mtc.id as taxComponentId, mtc.debit_account_id as debitAccountId, mtc.credit_account_id as creditAccountId, mtc.percentage as taxPercentage ");
|
"mtc.id as taxComponentId, mtc.debit_account_id as debitAccountId, mtc.credit_account_id as creditAccountId, mtc.percentage as taxPercentage ");
|
||||||
sqlBuilder.append("from m_savings_account sa ");
|
sqlBuilder.append("from m_savings_account sa ");
|
||||||
@@ -356,6 +359,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
|||||||
"left join acc_product_mapping apm on apm.product_type = 2 and apm.product_id = sp.id and apm.financial_account_type=3 ");
|
"left join acc_product_mapping apm on apm.product_type = 2 and apm.product_id = sp.id and apm.financial_account_type=3 ");
|
||||||
sqlBuilder.append(
|
sqlBuilder.append(
|
||||||
"left join acc_product_mapping apm1 on apm1.product_type = 2 and apm1.product_id = sp.id and apm1.financial_account_type=2 ");
|
"left join acc_product_mapping apm1 on apm1.product_type = 2 and apm1.product_id = sp.id and apm1.financial_account_type=2 ");
|
||||||
|
sqlBuilder.append("left join acc_product_mapping apm2 on apm2.product_id = sp.id and apm2.financial_account_type=18 ");
|
||||||
|
sqlBuilder.append("left join acc_product_mapping apm3 on apm3.product_id = sp.id and apm3.financial_account_type = 11 ");
|
||||||
|
sqlBuilder.append("left join acc_product_mapping apm4 on apm4.product_id = sp.id and apm4.financial_account_type = 17 ");
|
||||||
|
|
||||||
this.schemaSql = sqlBuilder.toString();
|
this.schemaSql = sqlBuilder.toString();
|
||||||
}
|
}
|
||||||
@@ -409,6 +415,11 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
|||||||
final Long glAccountIdForInterestOnSavings = rs.getLong("glAccountIdForInterestOnSavings");
|
final Long glAccountIdForInterestOnSavings = rs.getLong("glAccountIdForInterestOnSavings");
|
||||||
final Long glAccountIdForSavingsControl = rs.getLong("glAccountIdForSavingsControl");
|
final Long glAccountIdForSavingsControl = rs.getLong("glAccountIdForSavingsControl");
|
||||||
|
|
||||||
|
final Long glAccountIdForOverdraftPorfolio = rs.getLong("glAccountIdForOverdraftPorfolio");
|
||||||
|
final Long glAccountIdForInterestReceivable = rs.getLong("glAccountIdForInterestReceivable");
|
||||||
|
|
||||||
|
final Long glAccountIdForInterestPayable = rs.getLong("glAccountIdForInterestPayable");
|
||||||
|
|
||||||
final Long productId = rs.getLong("productId");
|
final Long productId = rs.getLong("productId");
|
||||||
final Integer accountType = rs.getInt("accountingType");
|
final Integer accountType = rs.getInt("accountingType");
|
||||||
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountType);
|
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountType);
|
||||||
@@ -565,6 +576,12 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
|||||||
savingsAccountData.setClientData(clientData);
|
savingsAccountData.setClientData(clientData);
|
||||||
savingsAccountData.setGroupGeneralData(groupGeneralData);
|
savingsAccountData.setGroupGeneralData(groupGeneralData);
|
||||||
savingsAccountData.setSavingsProduct(savingsProductData);
|
savingsAccountData.setSavingsProduct(savingsProductData);
|
||||||
|
|
||||||
|
savingsAccountData.setGlAccountIdForInterestReceivable(glAccountIdForInterestReceivable);
|
||||||
|
savingsAccountData.setGlAccountIdForOverdraftPorfolio(glAccountIdForOverdraftPorfolio);
|
||||||
|
|
||||||
|
savingsAccountData.setGlAccountIdForInterestPayable(glAccountIdForInterestPayable);
|
||||||
|
|
||||||
savingsAccountData.setGlAccountIdForInterestOnSavings(glAccountIdForInterestOnSavings);
|
savingsAccountData.setGlAccountIdForInterestOnSavings(glAccountIdForInterestOnSavings);
|
||||||
savingsAccountData.setGlAccountIdForSavingsControl(glAccountIdForSavingsControl);
|
savingsAccountData.setGlAccountIdForSavingsControl(glAccountIdForSavingsControl);
|
||||||
}
|
}
|
||||||
|
|||||||
+18
@@ -595,6 +595,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
|||||||
for (SavingsAccountTransactionData accountTransaction : transactions) {
|
for (SavingsAccountTransactionData accountTransaction : transactions) {
|
||||||
if (accountTransaction.getId() == null) {
|
if (accountTransaction.getId() == null) {
|
||||||
savingsAccountData.setNewSavingsAccountTransactionData(accountTransaction);
|
savingsAccountData.setNewSavingsAccountTransactionData(accountTransaction);
|
||||||
|
selectAccountId(accountTransaction, savingsAccountData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -604,6 +605,23 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
|||||||
return savingsAccountData;
|
return savingsAccountData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void selectAccountId(SavingsAccountTransactionData accountTransaction, SavingsAccountData savingsAccountData) {
|
||||||
|
SavingsAccountTransactionType transactionType = SavingsAccountTransactionType
|
||||||
|
.fromInt(accountTransaction.getTransactionType().getId().intValue());
|
||||||
|
if (transactionType.isOverDraftInterestPosting()) {
|
||||||
|
if (MathUtil.isGreaterThanZero(accountTransaction.getRunningBalance())) {
|
||||||
|
accountTransaction.setAccountDebit(savingsAccountData.getGlAccountIdForSavingsControl());
|
||||||
|
accountTransaction.setAccountCredit(savingsAccountData.getGlAccountIdForInterestReceivable());
|
||||||
|
} else {
|
||||||
|
accountTransaction.setAccountDebit(savingsAccountData.getGlAccountIdForOverdraftPorfolio());
|
||||||
|
accountTransaction.setAccountCredit(savingsAccountData.getGlAccountIdForInterestReceivable());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
accountTransaction.setAccountDebit(savingsAccountData.getGlAccountIdForInterestPayable());
|
||||||
|
accountTransaction.setAccountCredit(savingsAccountData.getGlAccountIdForSavingsControl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult reverseTransaction(final Long savingsId, final Long transactionId,
|
public CommandProcessingResult reverseTransaction(final Long savingsId, final Long transactionId,
|
||||||
final boolean allowAccountTransferModification, final JsonCommand command) {
|
final boolean allowAccountTransferModification, final JsonCommand command) {
|
||||||
|
|||||||
+25
-25
@@ -108,33 +108,33 @@ public class SavingsSchedularInterestPoster {
|
|||||||
for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList) {
|
for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList) {
|
||||||
if (savingsAccountTransactionData.getId() == null) {
|
if (savingsAccountTransactionData.getId() == null) {
|
||||||
final String key = savingsAccountTransactionData.getRefNo();
|
final String key = savingsAccountTransactionData.getRefNo();
|
||||||
if (savingsAccountTransactionDataHashMap.containsKey(key)) {
|
final Boolean isOverdraft = savingsAccountTransactionData.getIsOverdraft();
|
||||||
final SavingsAccountTransactionData dataFromFetch = savingsAccountTransactionDataHashMap.get(key);
|
final SavingsAccountTransactionData dataFromFetch = savingsAccountTransactionDataHashMap.get(key);
|
||||||
savingsAccountTransactionData.setId(dataFromFetch.getId());
|
savingsAccountTransactionData.setId(dataFromFetch.getId());
|
||||||
if (savingsAccountData.getGlAccountIdForSavingsControl() != 0
|
if (savingsAccountData.getGlAccountIdForSavingsControl() != 0
|
||||||
&& savingsAccountData.getGlAccountIdForInterestOnSavings() != 0) {
|
&& savingsAccountData.getGlAccountIdForInterestOnSavings() != 0) {
|
||||||
OffsetDateTime auditDatetime = DateUtils.getAuditOffsetDateTime();
|
OffsetDateTime auditDatetime = DateUtils.getAuditOffsetDateTime();
|
||||||
paramsForGLInsertion.add(new Object[] { savingsAccountData.getGlAccountIdForSavingsControl(),
|
paramsForGLInsertion.add(
|
||||||
savingsAccountData.getOfficeId(), null, currencyCode,
|
new Object[] { savingsAccountTransactionData.getAccountCredit(), savingsAccountData.getOfficeId(), null,
|
||||||
SAVINGS_TRANSACTION_IDENTIFIER + savingsAccountTransactionData.getId().toString(),
|
currencyCode, SAVINGS_TRANSACTION_IDENTIFIER + savingsAccountTransactionData.getId().toString(),
|
||||||
savingsAccountTransactionData.getId(), null, false, null, false,
|
savingsAccountTransactionData.getId(), null, false, null, false,
|
||||||
savingsAccountTransactionData.getTransactionDate(), JournalEntryType.CREDIT.getValue().longValue(),
|
savingsAccountTransactionData.getTransactionDate(), JournalEntryType.CREDIT.getValue().longValue(),
|
||||||
savingsAccountTransactionData.getAmount(), null, JournalEntryType.CREDIT.getValue().longValue(),
|
savingsAccountTransactionData.getAmount(), null, JournalEntryType.CREDIT.getValue().longValue(),
|
||||||
savingsAccountData.getId(), auditDatetime, auditDatetime, false, BigDecimal.ZERO, BigDecimal.ZERO, null,
|
savingsAccountData.getId(), auditDatetime, auditDatetime, false, BigDecimal.ZERO, BigDecimal.ZERO,
|
||||||
savingsAccountTransactionData.getTransactionDate(), null, userId, userId,
|
null, savingsAccountTransactionData.getTransactionDate(), null, userId, userId,
|
||||||
DateUtils.getBusinessLocalDate() });
|
DateUtils.getBusinessLocalDate() });
|
||||||
|
|
||||||
paramsForGLInsertion.add(new Object[] { savingsAccountData.getGlAccountIdForInterestOnSavings(),
|
paramsForGLInsertion
|
||||||
savingsAccountData.getOfficeId(), null, currencyCode,
|
.add(new Object[] { savingsAccountTransactionData.getAccountDebit(), savingsAccountData.getOfficeId(), null,
|
||||||
SAVINGS_TRANSACTION_IDENTIFIER + savingsAccountTransactionData.getId().toString(),
|
currencyCode, SAVINGS_TRANSACTION_IDENTIFIER + savingsAccountTransactionData.getId().toString(),
|
||||||
savingsAccountTransactionData.getId(), null, false, null, false,
|
savingsAccountTransactionData.getId(), null, false, null, false,
|
||||||
savingsAccountTransactionData.getTransactionDate(), JournalEntryType.DEBIT.getValue().longValue(),
|
savingsAccountTransactionData.getTransactionDate(), JournalEntryType.DEBIT.getValue().longValue(),
|
||||||
savingsAccountTransactionData.getAmount(), null, JournalEntryType.DEBIT.getValue().longValue(),
|
savingsAccountTransactionData.getAmount(), null, JournalEntryType.DEBIT.getValue().longValue(),
|
||||||
savingsAccountData.getId(), auditDatetime, auditDatetime, false, BigDecimal.ZERO, BigDecimal.ZERO, null,
|
savingsAccountData.getId(), auditDatetime, auditDatetime, false, BigDecimal.ZERO, BigDecimal.ZERO,
|
||||||
savingsAccountTransactionData.getTransactionDate(), null, userId, userId,
|
null, savingsAccountTransactionData.getTransactionDate(), null, userId, userId,
|
||||||
DateUtils.getBusinessLocalDate() });
|
DateUtils.getBusinessLocalDate() });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+443
@@ -0,0 +1,443 @@
|
|||||||
|
/**
|
||||||
|
* 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 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.math.MathContext;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
|
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
|
||||||
|
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.SchedulerJobHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.Account;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
|
||||||
|
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class SavingsInterestPostingTest {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SavingsInterestPostingTest.class);
|
||||||
|
private static ResponseSpecification responseSpec;
|
||||||
|
private static RequestSpecification requestSpec;
|
||||||
|
private AccountHelper accountHelper;
|
||||||
|
private SavingsAccountHelper savingsAccountHelper;
|
||||||
|
private SchedulerJobHelper schedulerJobHelper;
|
||||||
|
public static final String MINIMUM_OPENING_BALANCE = "1000.0";
|
||||||
|
private GlobalConfigurationHelper globalConfigurationHelper;
|
||||||
|
private SavingsProductHelper productHelper;
|
||||||
|
private JournalEntryHelper journalEntryHelper;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
Utils.initializeRESTAssured();
|
||||||
|
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||||
|
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||||
|
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||||
|
this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec);
|
||||||
|
this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
|
||||||
|
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||||
|
this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
|
||||||
|
this.globalConfigurationHelper = new GlobalConfigurationHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPostInterestWithOverdraftProduct() {
|
||||||
|
try {
|
||||||
|
final String amount = "10000";
|
||||||
|
final String jobName = "Post Interest For Savings";
|
||||||
|
|
||||||
|
final Account assetAccount = accountHelper.createAssetAccount();
|
||||||
|
final Account incomeAccount = accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = accountHelper.createExpenseAccount();
|
||||||
|
final Account liabilityAccount = accountHelper.createLiabilityAccount();
|
||||||
|
final Account interestReceivableAccount = accountHelper.createAssetAccount("interestReceivableAccount");
|
||||||
|
final Account savingsControlAccount = accountHelper.createLiabilityAccount("Savings Control");
|
||||||
|
final Account interestPayableAccount = accountHelper.createLiabilityAccount("Interest Payable");
|
||||||
|
|
||||||
|
final Integer productId = createSavingsProductWithAccrualAccountingWithOutOverdraftAllowed(
|
||||||
|
interestPayableAccount.getAccountID().toString(), savingsControlAccount.getAccountID().toString(),
|
||||||
|
interestReceivableAccount.getAccountID().toString(), assetAccount, incomeAccount, expenseAccount, liabilityAccount);
|
||||||
|
|
||||||
|
final Integer clientId = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2025");
|
||||||
|
final LocalDate startDate = LocalDate.of(LocalDate.now(Utils.getZoneIdOfTenant()).getYear(), 2, 1);
|
||||||
|
final String startDateString = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(startDate);
|
||||||
|
|
||||||
|
final Integer accountId = savingsAccountHelper.applyForSavingsApplicationOnDate(clientId, productId,
|
||||||
|
SavingsAccountHelper.ACCOUNT_TYPE_INDIVIDUAL, startDateString);
|
||||||
|
savingsAccountHelper.approveSavingsOnDate(accountId, startDateString);
|
||||||
|
savingsAccountHelper.activateSavings(accountId, startDateString);
|
||||||
|
savingsAccountHelper.depositToSavingsAccount(accountId, amount, startDateString, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
|
||||||
|
// Simulate time passing - update business date to March
|
||||||
|
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||||
|
new PutGlobalConfigurationsRequest().enabled(true));
|
||||||
|
LocalDate marchDate = LocalDate.of(startDate.getYear(), 3, 1);
|
||||||
|
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, marchDate);
|
||||||
|
|
||||||
|
schedulerJobHelper.executeAndAwaitJob(jobName);
|
||||||
|
|
||||||
|
long days = ChronoUnit.DAYS.between(startDate, marchDate);
|
||||||
|
BigDecimal expected = calcInterestPosting(productHelper, amount, days);
|
||||||
|
|
||||||
|
List<HashMap> txs = getInterestTransactions(accountId);
|
||||||
|
for (HashMap tx : txs) {
|
||||||
|
Assertions.assertEquals(expected, BigDecimal.valueOf(((Double) tx.get("amount"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
long interestCount = countInterestOnDate(accountId, marchDate);
|
||||||
|
long overdraftCount = countOverdraftOnDate(accountId, marchDate);
|
||||||
|
Assertions.assertEquals(1L, interestCount, "Expected exactly one INTEREST posting on posting date");
|
||||||
|
Assertions.assertEquals(0L, overdraftCount, "Expected NO OVERDRAFT posting on posting date");
|
||||||
|
} finally {
|
||||||
|
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||||
|
new PutGlobalConfigurationsRequest().enabled(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOverdraftInterestWithOverdraftProduct() {
|
||||||
|
try {
|
||||||
|
final String amount = "10000";
|
||||||
|
final String jobName = "Post Interest For Savings";
|
||||||
|
|
||||||
|
final Account assetAccount = accountHelper.createAssetAccount();
|
||||||
|
final Account incomeAccount = accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = accountHelper.createExpenseAccount();
|
||||||
|
final Account liabilityAccount = accountHelper.createLiabilityAccount();
|
||||||
|
final Account interestReceivableAccount = accountHelper.createAssetAccount("interestReceivableAccount");
|
||||||
|
final Account savingsControlAccount = accountHelper.createLiabilityAccount("Savings Control");
|
||||||
|
final Account interestPayableAccount = accountHelper.createLiabilityAccount("Interest Payable");
|
||||||
|
|
||||||
|
final Integer productId = createSavingsProductWithAccrualAccountingWithOutOverdraftAllowed(
|
||||||
|
interestPayableAccount.getAccountID().toString(), savingsControlAccount.getAccountID().toString(),
|
||||||
|
interestReceivableAccount.getAccountID().toString(), assetAccount, incomeAccount, expenseAccount, liabilityAccount);
|
||||||
|
|
||||||
|
final Integer clientId = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2025");
|
||||||
|
final LocalDate startDate = LocalDate.of(LocalDate.now(Utils.getZoneIdOfTenant()).getYear(), 2, 1);
|
||||||
|
final String startDateString = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(startDate);
|
||||||
|
|
||||||
|
final Integer accountId = savingsAccountHelper.applyForSavingsApplicationOnDate(clientId, productId,
|
||||||
|
SavingsAccountHelper.ACCOUNT_TYPE_INDIVIDUAL, startDateString);
|
||||||
|
savingsAccountHelper.approveSavingsOnDate(accountId, startDateString);
|
||||||
|
savingsAccountHelper.activateSavings(accountId, startDateString);
|
||||||
|
savingsAccountHelper.withdrawalFromSavingsAccount(accountId, amount, startDateString, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
|
||||||
|
// Simulate time passing - update business date to March
|
||||||
|
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||||
|
new PutGlobalConfigurationsRequest().enabled(true));
|
||||||
|
LocalDate marchDate = LocalDate.of(startDate.getYear(), 3, 1);
|
||||||
|
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, marchDate);
|
||||||
|
|
||||||
|
schedulerJobHelper.executeAndAwaitJob(jobName);
|
||||||
|
|
||||||
|
long days = ChronoUnit.DAYS.between(startDate, marchDate);
|
||||||
|
BigDecimal expected = calcOverdraftPosting(productHelper, amount, days);
|
||||||
|
|
||||||
|
List<HashMap> txs = getInterestTransactions(accountId);
|
||||||
|
Assertions.assertEquals(expected, BigDecimal.valueOf(((Double) txs.get(0).get("amount"))));
|
||||||
|
|
||||||
|
BigDecimal runningBalance = BigDecimal.valueOf(((Double) txs.get(0).get("runningBalance")));
|
||||||
|
Assertions.assertTrue(MathUtil.isLessThanZero(runningBalance), "Running balance is not less than zero");
|
||||||
|
|
||||||
|
long interestCount = countInterestOnDate(accountId, marchDate);
|
||||||
|
long overdraftCount = countOverdraftOnDate(accountId, marchDate);
|
||||||
|
Assertions.assertEquals(0L, interestCount, "Expected NO INTEREST posting on posting date");
|
||||||
|
Assertions.assertEquals(1L, overdraftCount, "Expected exactly one OVERDRAFT posting on posting date");
|
||||||
|
} finally {
|
||||||
|
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||||
|
new PutGlobalConfigurationsRequest().enabled(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOverdraftAndInterestPosting_WithOverdraftProduct_WhitBalanceLessZero() {
|
||||||
|
try {
|
||||||
|
final String amountDeposit = "10000";
|
||||||
|
final String amountWithdrawal = "20000";
|
||||||
|
final String jobName = "Post Interest For Savings";
|
||||||
|
|
||||||
|
final Account assetAccount = accountHelper.createAssetAccount();
|
||||||
|
final Account incomeAccount = accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = accountHelper.createExpenseAccount();
|
||||||
|
final Account liabilityAccount = accountHelper.createLiabilityAccount();
|
||||||
|
final Account interestReceivableAccount = accountHelper.createAssetAccount("interestReceivableAccount");
|
||||||
|
final Account savingsControlAccount = accountHelper.createLiabilityAccount("Savings Control");
|
||||||
|
final Account interestPayableAccount = accountHelper.createLiabilityAccount("Interest Payable");
|
||||||
|
|
||||||
|
final Integer productId = createSavingsProductWithAccrualAccountingWithOutOverdraftAllowed(
|
||||||
|
interestPayableAccount.getAccountID().toString(), savingsControlAccount.getAccountID().toString(),
|
||||||
|
interestReceivableAccount.getAccountID().toString(), assetAccount, incomeAccount, expenseAccount, liabilityAccount);
|
||||||
|
|
||||||
|
final Integer clientId = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2025");
|
||||||
|
final LocalDate startDate = LocalDate.of(LocalDate.now(Utils.getZoneIdOfTenant()).getYear(), 2, 1);
|
||||||
|
final String startStr = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(startDate);
|
||||||
|
|
||||||
|
final Integer accountId = savingsAccountHelper.applyForSavingsApplicationOnDate(clientId, productId,
|
||||||
|
SavingsAccountHelper.ACCOUNT_TYPE_INDIVIDUAL, startStr);
|
||||||
|
savingsAccountHelper.approveSavingsOnDate(accountId, startStr);
|
||||||
|
savingsAccountHelper.activateSavings(accountId, startStr);
|
||||||
|
savingsAccountHelper.depositToSavingsAccount(accountId, amountDeposit, startStr, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
|
||||||
|
final LocalDate withdrawalDate = LocalDate.of(startDate.getYear(), 2, 16);
|
||||||
|
final String withdrawalStr = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(withdrawalDate);
|
||||||
|
savingsAccountHelper.withdrawalFromSavingsAccount(accountId, amountWithdrawal, withdrawalStr,
|
||||||
|
CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
|
||||||
|
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||||
|
new PutGlobalConfigurationsRequest().enabled(true));
|
||||||
|
LocalDate marchDate = LocalDate.of(startDate.getYear(), 3, 1);
|
||||||
|
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, marchDate);
|
||||||
|
|
||||||
|
schedulerJobHelper.executeAndAwaitJob(jobName);
|
||||||
|
|
||||||
|
List<HashMap> txs = getInterestTransactions(accountId);
|
||||||
|
for (HashMap tx : txs) {
|
||||||
|
BigDecimal amt = BigDecimal.valueOf(((Double) tx.get("amount")));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> typeMap = (Map<String, Object>) tx.get("transactionType");
|
||||||
|
SavingsAccountTransactionType type = SavingsAccountTransactionType.fromInt(((Double) typeMap.get("id")).intValue());
|
||||||
|
|
||||||
|
if (type.isInterestPosting()) {
|
||||||
|
long days = ChronoUnit.DAYS.between(startDate, withdrawalDate);
|
||||||
|
BigDecimal expected = calcInterestPosting(productHelper, amountDeposit, days);
|
||||||
|
Assertions.assertEquals(expected, amt);
|
||||||
|
} else {
|
||||||
|
long days = ChronoUnit.DAYS.between(withdrawalDate, marchDate);
|
||||||
|
BigDecimal overdraftBase = new BigDecimal(amountWithdrawal).subtract(new BigDecimal(amountDeposit));
|
||||||
|
BigDecimal expected = calcOverdraftPosting(productHelper, overdraftBase.toString(), days);
|
||||||
|
Assertions.assertEquals(expected, amt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assertions.assertEquals(1L, countInterestOnDate(accountId, marchDate), "Expected exactly one INTEREST posting on posting date");
|
||||||
|
Assertions.assertEquals(1L, countOverdraftOnDate(accountId, marchDate),
|
||||||
|
"Expected exactly one OVERDRAFT posting on posting date");
|
||||||
|
} finally {
|
||||||
|
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||||
|
new PutGlobalConfigurationsRequest().enabled(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOverdraftAndInterestPosting_WithOverdraftProduct_WhitBalanceGreaterZero() {
|
||||||
|
try {
|
||||||
|
final String amountDeposit = "20000";
|
||||||
|
final String amountWithdrawal = "10000";
|
||||||
|
final String jobName = "Post Interest For Savings";
|
||||||
|
|
||||||
|
final Account assetAccount = accountHelper.createAssetAccount();
|
||||||
|
final Account incomeAccount = accountHelper.createIncomeAccount();
|
||||||
|
final Account expenseAccount = accountHelper.createExpenseAccount();
|
||||||
|
final Account liabilityAccount = accountHelper.createLiabilityAccount();
|
||||||
|
final Account interestReceivableAccount = accountHelper.createAssetAccount("interestReceivableAccount");
|
||||||
|
final Account savingsControlAccount = accountHelper.createLiabilityAccount("Savings Control");
|
||||||
|
final Account interestPayableAccount = accountHelper.createLiabilityAccount("Interest Payable");
|
||||||
|
|
||||||
|
final Integer productId = createSavingsProductWithAccrualAccountingWithOutOverdraftAllowed(
|
||||||
|
interestPayableAccount.getAccountID().toString(), savingsControlAccount.getAccountID().toString(),
|
||||||
|
interestReceivableAccount.getAccountID().toString(), assetAccount, incomeAccount, expenseAccount, liabilityAccount);
|
||||||
|
|
||||||
|
final Integer clientId = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2025");
|
||||||
|
final LocalDate startDate = LocalDate.of(LocalDate.now(Utils.getZoneIdOfTenant()).getYear(), 2, 1);
|
||||||
|
final String startStr = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(startDate);
|
||||||
|
|
||||||
|
final Integer accountId = savingsAccountHelper.applyForSavingsApplicationOnDate(clientId, productId,
|
||||||
|
SavingsAccountHelper.ACCOUNT_TYPE_INDIVIDUAL, startStr);
|
||||||
|
savingsAccountHelper.approveSavingsOnDate(accountId, startStr);
|
||||||
|
savingsAccountHelper.activateSavings(accountId, startStr);
|
||||||
|
savingsAccountHelper.withdrawalFromSavingsAccount(accountId, amountWithdrawal, startStr, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
|
||||||
|
final LocalDate depositDate = LocalDate.of(startDate.getYear(), 2, 16);
|
||||||
|
final String depositStr = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(depositDate);
|
||||||
|
savingsAccountHelper.depositToSavingsAccount(accountId, amountDeposit, depositStr, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
|
||||||
|
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||||
|
new PutGlobalConfigurationsRequest().enabled(true));
|
||||||
|
LocalDate marchDate = LocalDate.of(startDate.getYear(), 3, 1);
|
||||||
|
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, marchDate);
|
||||||
|
|
||||||
|
schedulerJobHelper.executeAndAwaitJob(jobName);
|
||||||
|
|
||||||
|
List<HashMap> txs = getInterestTransactions(accountId);
|
||||||
|
for (HashMap tx : txs) {
|
||||||
|
BigDecimal amt = BigDecimal.valueOf(((Double) tx.get("amount")));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> typeMap = (Map<String, Object>) tx.get("transactionType");
|
||||||
|
SavingsAccountTransactionType type = SavingsAccountTransactionType.fromInt(((Double) typeMap.get("id")).intValue());
|
||||||
|
|
||||||
|
if (type.isOverDraftInterestPosting()) {
|
||||||
|
long days = ChronoUnit.DAYS.between(startDate, depositDate);
|
||||||
|
BigDecimal expected = calcOverdraftPosting(productHelper, amountWithdrawal, days);
|
||||||
|
Assertions.assertEquals(expected, amt);
|
||||||
|
} else {
|
||||||
|
long days = ChronoUnit.DAYS.between(depositDate, marchDate);
|
||||||
|
BigDecimal positiveBase = new BigDecimal(amountDeposit).subtract(new BigDecimal(amountWithdrawal));
|
||||||
|
BigDecimal expected = calcInterestPosting(productHelper, positiveBase.toString(), days);
|
||||||
|
Assertions.assertEquals(expected, amt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assertions.assertEquals(1L, countOverdraftOnDate(accountId, marchDate),
|
||||||
|
"Expected exactly one OVERDRAFT posting on posting date");
|
||||||
|
Assertions.assertEquals(1L, countInterestOnDate(accountId, marchDate), "Expected exactly one INTEREST posting on posting date");
|
||||||
|
} finally {
|
||||||
|
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||||
|
new PutGlobalConfigurationsRequest().enabled(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<HashMap> getInterestTransactions(Integer savingsAccountId) {
|
||||||
|
List<HashMap> all = savingsAccountHelper.getSavingsTransactions(savingsAccountId);
|
||||||
|
List<HashMap> filtered = new ArrayList<>();
|
||||||
|
for (HashMap tx : all) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> txType = (Map<String, Object>) tx.get("transactionType");
|
||||||
|
SavingsAccountTransactionType type = SavingsAccountTransactionType.fromInt(((Double) txType.get("id")).intValue());
|
||||||
|
if (type.isInterestPosting() || type.isOverDraftInterestPosting()) {
|
||||||
|
filtered.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer createSavingsProductWithAccrualAccountingWithOutOverdraftAllowed(final String interestPayableAccount,
|
||||||
|
final String savingsControlAccount, final String interestReceivableAccount, final Account... accounts) {
|
||||||
|
|
||||||
|
LOG.info("------------------------------CREATING NEW SAVINGS PRODUCT WITHOUT OVERDRAFT ---------------------------------------");
|
||||||
|
this.productHelper = new SavingsProductHelper().withOverDraftRate("100000", "21")
|
||||||
|
.withAccountInterestReceivables(interestReceivableAccount).withSavingsControlAccountId(savingsControlAccount)
|
||||||
|
.withInterestPayableAccountId(interestPayableAccount).withInterestCompoundingPeriodTypeAsAnnually()
|
||||||
|
.withInterestPostingPeriodTypeAsMonthly().withInterestCalculationPeriodTypeAsDailyBalance()
|
||||||
|
.withAccountingRuleAsAccrualBased(accounts);
|
||||||
|
|
||||||
|
final String savingsProductJSON = this.productHelper.build();
|
||||||
|
return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal calcInterestPosting(SavingsProductHelper productHelper, String amount, long days) {
|
||||||
|
BigDecimal rate = productHelper.getNominalAnnualInterestRate().divide(new BigDecimal("100.00"));
|
||||||
|
BigDecimal principal = new BigDecimal(amount);
|
||||||
|
BigDecimal dayFactor = BigDecimal.ONE.divide(productHelper.getInterestCalculationDaysInYearType(), MathContext.DECIMAL64);
|
||||||
|
BigDecimal dailyRate = rate.multiply(dayFactor, MathContext.DECIMAL64);
|
||||||
|
BigDecimal periodRate = dailyRate.multiply(BigDecimal.valueOf(days), MathContext.DECIMAL64);
|
||||||
|
return principal.multiply(periodRate, MathContext.DECIMAL64).setScale(productHelper.getDecimalCurrency(), RoundingMode.HALF_EVEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal calcOverdraftPosting(SavingsProductHelper productHelper, String amount, long days) {
|
||||||
|
BigDecimal rate = productHelper.getNominalAnnualInterestRateOverdraft().divide(new BigDecimal("100.00"));
|
||||||
|
BigDecimal principal = new BigDecimal(amount);
|
||||||
|
BigDecimal dayFactor = BigDecimal.ONE.divide(productHelper.getInterestCalculationDaysInYearType(), MathContext.DECIMAL64);
|
||||||
|
BigDecimal dailyRate = rate.multiply(dayFactor, MathContext.DECIMAL64);
|
||||||
|
BigDecimal periodRate = dailyRate.multiply(BigDecimal.valueOf(days), MathContext.DECIMAL64);
|
||||||
|
return principal.multiply(periodRate, MathContext.DECIMAL64).setScale(productHelper.getDecimalCurrency(), RoundingMode.HALF_EVEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// Helpers robustos de FECHA/TIPO
|
||||||
|
// =========================
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private LocalDate coerceToLocalDate(HashMap tx) {
|
||||||
|
// Posibles claves de fecha devueltas por Fineract
|
||||||
|
String[] candidateKeys = new String[] { "date", "transactionDate", "submittedOnDate", "createdDate" };
|
||||||
|
|
||||||
|
for (String key : candidateKeys) {
|
||||||
|
Object v = tx.get(key);
|
||||||
|
if (v == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caso 1: arreglo [yyyy, MM, dd]
|
||||||
|
if (v instanceof List<?>) {
|
||||||
|
List<?> arr = (List<?>) v;
|
||||||
|
if (arr.size() >= 3 && arr.get(0) instanceof Number && arr.get(1) instanceof Number && arr.get(2) instanceof Number) {
|
||||||
|
int year = ((Number) arr.get(0)).intValue();
|
||||||
|
int month = ((Number) arr.get(1)).intValue();
|
||||||
|
int day = ((Number) arr.get(2)).intValue();
|
||||||
|
return LocalDate.of(year, month, day);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caso 2: string con distintos formatos
|
||||||
|
if (v instanceof String) {
|
||||||
|
String s = (String) v;
|
||||||
|
DateTimeFormatter[] fmts = new DateTimeFormatter[] { DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US),
|
||||||
|
DateTimeFormatter.ofPattern("dd MMM yyyy", Locale.US), DateTimeFormatter.ofPattern("yyyy-MM-dd") };
|
||||||
|
for (DateTimeFormatter f : fmts) {
|
||||||
|
try {
|
||||||
|
return LocalDate.parse(s, f);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Si ninguna clave/forma se pudo parsear
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDate(HashMap tx, LocalDate expected) {
|
||||||
|
LocalDate got = coerceToLocalDate(tx);
|
||||||
|
return got != null && got.isEqual(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private SavingsAccountTransactionType txType(HashMap tx) {
|
||||||
|
Map<String, Object> typeMap = (Map<String, Object>) tx.get("transactionType");
|
||||||
|
return SavingsAccountTransactionType.fromInt(((Double) typeMap.get("id")).intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private long countInterestOnDate(Integer accountId, LocalDate date) {
|
||||||
|
List<HashMap> all = savingsAccountHelper.getSavingsTransactions(accountId);
|
||||||
|
return all.stream().filter(tx -> isDate(tx, date)).map(this::txType).filter(SavingsAccountTransactionType::isInterestPosting)
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long countOverdraftOnDate(Integer accountId, LocalDate date) {
|
||||||
|
List<HashMap> all = savingsAccountHelper.getSavingsTransactions(accountId);
|
||||||
|
return all.stream().filter(tx -> isDate(tx, date)).map(this::txType)
|
||||||
|
.filter(SavingsAccountTransactionType::isOverDraftInterestPosting).count();
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-2
@@ -168,8 +168,8 @@ public class SavingsProductHelper {
|
|||||||
map.put("interestReceivableAccountId", this.interestReceivableAccountId);
|
map.put("interestReceivableAccountId", this.interestReceivableAccountId);
|
||||||
}
|
}
|
||||||
if (this.accountingRule.equals(ACCRUAL_PERIODIC)) {
|
if (this.accountingRule.equals(ACCRUAL_PERIODIC)) {
|
||||||
if (this.interestReceivableAccountId != null) {
|
if (this.savingsControlAccountId != null) {
|
||||||
map.put("interestReceivableAccountId", this.interestReceivableAccountId);
|
map.put("savingsControlAccountId", this.savingsControlAccountId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +202,11 @@ public class SavingsProductHelper {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SavingsProductHelper withInterestCompoundingPeriodTypeAsAnnually() {
|
||||||
|
this.interestCompoundingPeriodType = ANNUAL;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public SavingsProductHelper withInterestPostingPeriodTypeAsMonthly() {
|
public SavingsProductHelper withInterestPostingPeriodTypeAsMonthly() {
|
||||||
this.interestPostingPeriodType = MONTHLY;
|
this.interestPostingPeriodType = MONTHLY;
|
||||||
return this;
|
return this;
|
||||||
@@ -357,6 +362,10 @@ public class SavingsProductHelper {
|
|||||||
return new BigDecimal(nominalAnnualInterestRate);
|
return new BigDecimal(nominalAnnualInterestRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigDecimal getNominalAnnualInterestRateOverdraft() {
|
||||||
|
return new BigDecimal(nominalAnnualInterestRateOverdraft);
|
||||||
|
}
|
||||||
|
|
||||||
public BigDecimal getInterestCalculationDaysInYearType() {
|
public BigDecimal getInterestCalculationDaysInYearType() {
|
||||||
return new BigDecimal(interestCalculationDaysInYearType);
|
return new BigDecimal(interestCalculationDaysInYearType);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user