galovics commented on a change in pull request #2025:
URL: https://github.com/apache/fineract/pull/2025#discussion_r799699402
##########
File path:
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
##########
@@ -1003,7 +1003,7 @@ protected void recalculateDailyBalances(final Money
openingAccountBalance, final
if (overdraftAmount.isGreaterThanZero()) {
accountTransaction.updateOverdraftAmount(overdraftAmount.getAmount());
}
- accountTransaction.updateRunningBalance(runningBalance);
+ // accountTransaction.updateRunningBalance(runningBalance);
Review comment:
I assume this comment was just left in as a mistake. Let's fix it.
##########
File path:
fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
##########
@@ -97,6 +103,9 @@
private final SavingsAccountTransactionRepository
savingsAccountTransactionRepository;
private final AccountTransfersReadPlatformService
accountTransfersReadPlatformService;
private final ChargeRepositoryWrapper chargeRepositoryWrapper;
+ private final JdbcTemplate jdbcTemplate;
+ private final DataSource dataSource;
+ private static final Logger LOG =
LoggerFactory.getLogger(AccountingProcessorHelper.class);
Review comment:
Hi @BLasan.
Are these 3 attributes used anywhere in the code? Right now I don't see any
usage. If they aren't, do you think it's worth removing them?
Thanks!
##########
File path:
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
##########
@@ -230,6 +246,495 @@ public SavingsAccountData retrieveOne(final Long
accountId) {
}
}
+ @Override
+ public List<SavingsAccountTransactionData>
retrieveAllTransactionData(final List<String> refNo) throws DataAccessException
{
+ String inSql = String.join(",", Collections.nCopies(refNo.size(),
"?"));
+ String sql = "select " +
this.savingsAccountTransactionsForBatchMapper.schema() + " where tr.ref_no in
(%s)";
+ Object[] params = new Object[refNo.size()];
+ int i = 0;
+ for (String element : refNo) {
+ params[i] = element;
+ i++;
+ }
+ return this.jdbcTemplate.query(String.format(sql, inSql),
this.savingsAccountTransactionsForBatchMapper, params);
+ }
+
+ @Override
+ public List<SavingsAccountData>
retrieveAllSavingsDataForInterestPosting(final boolean
backdatedTxnsAllowedTill, final int pageSize,
+ final Integer status, final Long maxSavingsId) {
+ LocalDate currentDate = DateUtils.getLocalDateOfTenant().minusDays(1);
+
+ String sql = "select " +
this.savingAccountMapperForInterestPosting.schema()
+ + "join (select a.id from m_savings_account a where a.id > ?
and a.status_enum = ? limit ?) b on b.id = sa.id ";
+ if (backdatedTxnsAllowedTill) {
+ sql = sql
+ + "where if (sa.interest_posted_till_date is not null,
tr.transaction_date >= sa.interest_posted_till_date, tr.transaction_date >=
sa.activatedon_date) ";
+ }
+
+ sql = sql + "and apm.product_type=2 and sa.interest_posted_till_date<"
+ java.sql.Date.valueOf(currentDate);
+ sql = sql + " order by sa.id, tr.transaction_date, tr.created_date,
tr.id";
Review comment:
Any chance we could use JPQL for this query instead of native SQL?
Probably we could gain some portability here in the future with JPQL. Right now
I don't see any native feature in the query hence the question.
##########
File path:
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
##########
@@ -230,6 +246,495 @@ public SavingsAccountData retrieveOne(final Long
accountId) {
}
}
+ @Override
+ public List<SavingsAccountTransactionData>
retrieveAllTransactionData(final List<String> refNo) throws DataAccessException
{
+ String inSql = String.join(",", Collections.nCopies(refNo.size(),
"?"));
+ String sql = "select " +
this.savingsAccountTransactionsForBatchMapper.schema() + " where tr.ref_no in
(%s)";
+ Object[] params = new Object[refNo.size()];
+ int i = 0;
+ for (String element : refNo) {
+ params[i] = element;
+ i++;
+ }
+ return this.jdbcTemplate.query(String.format(sql, inSql),
this.savingsAccountTransactionsForBatchMapper, params);
+ }
+
+ @Override
+ public List<SavingsAccountData>
retrieveAllSavingsDataForInterestPosting(final boolean
backdatedTxnsAllowedTill, final int pageSize,
+ final Integer status, final Long maxSavingsId) {
+ LocalDate currentDate = DateUtils.getLocalDateOfTenant().minusDays(1);
+
+ String sql = "select " +
this.savingAccountMapperForInterestPosting.schema()
+ + "join (select a.id from m_savings_account a where a.id > ?
and a.status_enum = ? limit ?) b on b.id = sa.id ";
+ if (backdatedTxnsAllowedTill) {
+ sql = sql
+ + "where if (sa.interest_posted_till_date is not null,
tr.transaction_date >= sa.interest_posted_till_date, tr.transaction_date >=
sa.activatedon_date) ";
+ }
+
+ sql = sql + "and apm.product_type=2 and sa.interest_posted_till_date<"
+ java.sql.Date.valueOf(currentDate);
+ sql = sql + " order by sa.id, tr.transaction_date, tr.created_date,
tr.id";
+
+ List<SavingsAccountData> savingsAccountDataList =
this.jdbcTemplate.query(sql, this.savingAccountMapperForInterestPosting,
+ new Object[] { maxSavingsId, status, pageSize });
+ for (SavingsAccountData savingsAccountData : savingsAccountDataList) {
+ this.savingAccountAssembler.assembleSavings(savingsAccountData);
+ }
+ return savingsAccountDataList;
+ }
+
+ private static final class SavingAccountMapperForInterestPosting
implements ResultSetExtractor<List<SavingsAccountData>> {
+
+ private final String schemaSql;
+
+ SavingAccountMapperForInterestPosting() {
+ final StringBuilder sqlBuilder = new StringBuilder(400);
+ sqlBuilder.append("sa.id as id, sa.account_no as accountNo,
sa.external_id as externalId, ");
+ sqlBuilder.append("sa.deposit_type_enum as depositType, ");
+ sqlBuilder.append("c.id as clientId, c.office_id as
clientOfficeId, ");
+ sqlBuilder.append("g.id as groupId, g.office_id as groupOfficeId,
");
+ sqlBuilder.append("sa.status_enum as statusEnum, ");
+ sqlBuilder.append("sa.sub_status_enum as subStatusEnum, ");
+ sqlBuilder.append("sa.submittedon_date as submittedOnDate,");
+
+ sqlBuilder.append("sa.rejectedon_date as rejectedOnDate,");
+
+ sqlBuilder.append("sa.withdrawnon_date as withdrawnOnDate,");
+
+ sqlBuilder.append("sa.approvedon_date as approvedOnDate,");
+
+ sqlBuilder.append("sa.activatedon_date as activatedOnDate,");
+
+ sqlBuilder.append("sa.closedon_date as closedOnDate,");
+
+ sqlBuilder.append(
+ "sa.currency_code as currencyCode, sa.currency_digits as
currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
+
+ sqlBuilder.append("sa.nominal_annual_interest_rate as
nominalAnnualInterestRate, ");
+ sqlBuilder.append("sa.interest_compounding_period_enum as
interestCompoundingPeriodType, ");
+ sqlBuilder.append("sa.interest_posting_period_enum as
interestPostingPeriodType, ");
+ sqlBuilder.append("sa.interest_calculation_type_enum as
interestCalculationType, ");
+ sqlBuilder.append("sa.interest_calculation_days_in_year_type_enum
as interestCalculationDaysInYearType, ");
+ sqlBuilder.append("sa.min_required_opening_balance as
minRequiredOpeningBalance, ");
+ sqlBuilder.append("sa.lockin_period_frequency as
lockinPeriodFrequency,");
+ sqlBuilder.append("sa.lockin_period_frequency_enum as
lockinPeriodFrequencyType, ");
+ sqlBuilder.append("sa.withdrawal_fee_for_transfer as
withdrawalFeeForTransfers, ");
+ sqlBuilder.append("sa.allow_overdraft as allowOverdraft, ");
+ sqlBuilder.append("sa.overdraft_limit as overdraftLimit, ");
+ sqlBuilder.append("sa.nominal_annual_interest_rate_overdraft as
nominalAnnualInterestRateOverdraft, ");
+ sqlBuilder.append("sa.min_overdraft_for_interest_calculation as
minOverdraftForInterestCalculation, ");
+ sqlBuilder.append("sa.total_deposits_derived as totalDeposits, ");
+ sqlBuilder.append("sa.total_withdrawals_derived as
totalWithdrawals, ");
+ sqlBuilder.append("sa.total_withdrawal_fees_derived as
totalWithdrawalFees, ");
+ sqlBuilder.append("sa.total_annual_fees_derived as
totalAnnualFees, ");
+ sqlBuilder.append("sa.total_interest_earned_derived as
totalInterestEarned, ");
+ sqlBuilder.append("sa.total_interest_posted_derived as
totalInterestPosted, ");
+ sqlBuilder.append("sa.total_overdraft_interest_derived as
totalOverdraftInterestDerived, ");
+ sqlBuilder.append("sa.account_balance_derived as accountBalance,
");
+ sqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge,
");
+ sqlBuilder.append("sa.total_penalty_charge_derived as
totalPenaltyCharge, ");
+ sqlBuilder.append("sa.min_balance_for_interest_calculation as
minBalanceForInterestCalculation,");
+ sqlBuilder.append("sa.min_required_balance as minRequiredBalance,
");
+ sqlBuilder.append("sa.enforce_min_required_balance as
enforceMinRequiredBalance, ");
+ sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds, ");
+ sqlBuilder.append("sa.withhold_tax as withHoldTax, ");
+ sqlBuilder.append("sa.total_withhold_tax_derived as
totalWithholdTax, ");
+ sqlBuilder.append("sa.last_interest_calculation_date as
lastInterestCalculationDate, ");
+ sqlBuilder.append("sa.total_savings_amount_on_hold as
onHoldAmount, ");
+ sqlBuilder.append("sa.interest_posted_till_date as
interestPostedTillDate, ");
+ sqlBuilder.append("tg.id as taxGroupId, ");
+ sqlBuilder.append("(select
IFNULL(max(sat.transaction_date),sa.activatedon_date) ");
+ sqlBuilder.append("from m_savings_account_transaction as sat ");
+ sqlBuilder.append("where sat.is_reversed = 0 ");
+ sqlBuilder.append("and sat.transaction_type_enum in (1,2) ");
+ sqlBuilder.append("and sat.savings_account_id = sa.id) as
lastActiveTransactionDate, ");
+ sqlBuilder.append("sp.id as productId, ");
+ sqlBuilder.append("sp.is_dormancy_tracking_active as
isDormancyTrackingActive, ");
+ sqlBuilder.append("sp.days_to_inactive as daysToInactive, ");
+ sqlBuilder.append("sp.days_to_dormancy as daysToDormancy, ");
+ sqlBuilder.append("sp.days_to_escheat as daysToEscheat, ");
+ sqlBuilder.append("sp.accounting_type as accountingType, ");
+ sqlBuilder.append("tr.id as transactionId,
tr.transaction_type_enum as transactionType, ");
+ sqlBuilder.append("tr.transaction_date as transactionDate,
tr.amount as transactionAmount,");
+ sqlBuilder.append("tr.created_date as
createdDate,tr.cumulative_balance_derived as cumulativeBalance,");
+ sqlBuilder.append("tr.running_balance_derived as runningBalance,
tr.is_reversed as reversed,");
+ sqlBuilder.append("tr.balance_end_date_derived as balanceEndDate,
tr.overdraft_amount_derived as overdraftAmount,");
+ sqlBuilder.append("tr.is_manual as manualTransaction,tr.office_id
as officeId, ");
+ sqlBuilder.append("pd.payment_type_id as
paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber,
");
+ sqlBuilder.append("pd.receipt_number as receiptNumber,
pd.bank_number as bankNumber,pd.routing_code as routingCode, ");
+ sqlBuilder.append("pt.value as paymentTypeName, ");
+ sqlBuilder.append("msacpb.amount as paidByAmount, msacpb.id as
chargesPaidById, ");
+ sqlBuilder.append(
+ "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("apm.gl_account_id as
glAccountIdForInterestOnSavings, apm1.gl_account_id as
glAccountIdForSavingsControl, ");
+ sqlBuilder.append(
+ "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("join m_savings_product sp ON sa.product_id =
sp.id ");
+ sqlBuilder.append("join m_currency curr on curr.code =
sa.currency_code ");
+ sqlBuilder.append("join m_savings_account_transaction tr on sa.id
= tr.savings_account_id ");
+ sqlBuilder.append("left join m_payment_detail pd on pd.id =
tr.payment_detail_id ");
+ sqlBuilder.append("left join m_payment_type pt on pt.id =
pd.payment_type_id ");
+ sqlBuilder.append("left join m_savings_account_charge_paid_by
msacpb on msacpb.savings_account_transaction_id = tr.id ");
+ sqlBuilder.append("left join m_savings_account_charge msac on
msac.id = msacpb.savings_account_charge_id ");
+ sqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
+ sqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
+ sqlBuilder.append("left join m_tax_group tg on tg.id =
sa.tax_group_id ");
+ sqlBuilder.append("left join
m_savings_account_transaction_tax_details txd on txd.savings_transaction_id =
tr.id ");
+ sqlBuilder.append("left join m_tax_component mtc on mtc.id =
txd.tax_component_id ");
+ sqlBuilder.append("left join acc_product_mapping apm on
apm.product_id = sp.id and apm.financial_account_type=3 ");
+ sqlBuilder.append("left join acc_product_mapping apm1 on
apm1.product_id = sp.id and apm1.financial_account_type=2 ");
+
+ this.schemaSql = sqlBuilder.toString();
+ }
+
+ public String schema() {
+ return this.schemaSql;
+ }
+
+ @Override
+ public List<SavingsAccountData> extractData(final ResultSet rs) throws
SQLException {
+
+ List<SavingsAccountData> savingsAccountDataList = new
ArrayList<>();
+ HashMap<String, Long> savingsMap = new HashMap<>();
+ String currencyCode = null;
+ Integer currencyDigits = null;
+ Integer inMultiplesOf = null;
+ CurrencyData currency = null;
+ HashMap<String, Long> transMap = new HashMap<>();
+ HashMap<String, Long> taxDetails = new HashMap<>();
+ HashMap<String, Long> chargeDetails = new HashMap<>();
+ SavingsAccountTransactionData savingsAccountTransactionData = null;
+ SavingsAccountData savingsAccountData = null;
+ int count = 0;
+
+ while (rs.next()) {
+ final Long id = rs.getLong("id");
+ final Long transactionId = rs.getLong("transactionId");
+ final Long taxDetailId =
JdbcSupport.getLongDefaultToNullIfZero(rs, "taxDetailsId");
+ final Long taxComponentId =
JdbcSupport.getLongDefaultToNullIfZero(rs, "taxComponentId");
+ final String accountNo = rs.getString("accountNo");
+ final Long chargeId = rs.getLong("chargeId");
+
+ if (!savingsMap.containsValue(id)) {
+ if (count > 0) {
+ savingsAccountDataList.add(savingsAccountData);
+ }
+ count++;
+ savingsMap.put("id", id);
+
+ final String externalId = rs.getString("externalId");
+ final Integer depositTypeId = rs.getInt("depositType");
+ final EnumOptionData depositType =
SavingsEnumerations.depositType(depositTypeId);
+ final Long groupId = JdbcSupport.getLong(rs, "groupId");
+ final Long groupOfficeId = JdbcSupport.getLong(rs,
"groupOfficeId");
+ final GroupGeneralData groupGeneralData = new
GroupGeneralData(groupId, groupOfficeId);
+
+ final Long clientId = JdbcSupport.getLong(rs, "clientId");
+ final Long clientOfficeId = JdbcSupport.getLong(rs,
"clientOfficeId");
+ final ClientData clientData =
ClientData.createClientForInterestPosting(clientId, clientOfficeId);
+
+ final Long glAccountIdForInterestOnSavings =
rs.getLong("glAccountIdForInterestOnSavings");
+ final Long glAccountIdForSavingsControl =
rs.getLong("glAccountIdForSavingsControl");
+
+ final Long productId = rs.getLong("productId");
+ final Integer accountType = rs.getInt("accountingType");
+ final AccountingRuleType accountingRuleType =
AccountingRuleType.fromInt(accountType);
+ final EnumOptionData enumOptionDataForAccounting = new
EnumOptionData(accountType.longValue(),
+ accountingRuleType.getCode(),
accountingRuleType.getValue().toString());
+ final SavingsProductData savingsProductData =
SavingsProductData.createForInterestPosting(productId,
+ enumOptionDataForAccounting);
+
+ final Integer statusEnum = JdbcSupport.getInteger(rs,
"statusEnum");
+ final SavingsAccountStatusEnumData status =
SavingsEnumerations.status(statusEnum);
+ final Integer subStatusEnum = JdbcSupport.getInteger(rs,
"subStatusEnum");
+ final SavingsAccountSubStatusEnumData subStatus =
SavingsEnumerations.subStatus(subStatusEnum);
+ final LocalDate lastActiveTransactionDate =
JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate");
+ final boolean isDormancyTrackingActive =
rs.getBoolean("isDormancyTrackingActive");
+ final Integer numDaysToInactive =
JdbcSupport.getInteger(rs, "daysToInactive");
+ final Integer numDaysToDormancy =
JdbcSupport.getInteger(rs, "daysToDormancy");
+ final Integer numDaysToEscheat =
JdbcSupport.getInteger(rs, "daysToEscheat");
+ Integer daysToInactive = null;
+ Integer daysToDormancy = null;
+ Integer daysToEscheat = null;
+
+ LocalDate localTenantDate =
DateUtils.getLocalDateOfTenant();
+ if (isDormancyTrackingActive &&
statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) {
+ if (subStatusEnum <
SavingsAccountSubStatusEnum.ESCHEAT.getValue()) {
+ daysToEscheat = Math.toIntExact(
+ ChronoUnit.DAYS.between(localTenantDate,
lastActiveTransactionDate.plusDays(numDaysToEscheat)));
+ }
+ if (subStatusEnum <
SavingsAccountSubStatusEnum.DORMANT.getValue()) {
+ daysToDormancy = Math.toIntExact(
+ ChronoUnit.DAYS.between(localTenantDate,
lastActiveTransactionDate.plusDays(numDaysToDormancy)));
+ }
+ if (subStatusEnum <
SavingsAccountSubStatusEnum.INACTIVE.getValue()) {
+ daysToInactive = Math.toIntExact(
+ ChronoUnit.DAYS.between(localTenantDate,
lastActiveTransactionDate.plusDays(numDaysToInactive)));
+ }
+ }
+ final LocalDate approvedOnDate =
JdbcSupport.getLocalDate(rs, "approvedOnDate");
+ final LocalDate withdrawnOnDate =
JdbcSupport.getLocalDate(rs, "withdrawnOnDate");
+ final LocalDate submittedOnDate =
JdbcSupport.getLocalDate(rs, "submittedOnDate");
+ final LocalDate activatedOnDate =
JdbcSupport.getLocalDate(rs, "activatedOnDate");
+ final LocalDate closedOnDate =
JdbcSupport.getLocalDate(rs, "closedOnDate");
+ final SavingsAccountApplicationTimelineData timeline = new
SavingsAccountApplicationTimelineData(submittedOnDate, null,
+ null, null, null, null, null, null,
withdrawnOnDate, null, null, null, approvedOnDate, null, null, null,
+ activatedOnDate, null, null, null, closedOnDate,
null, null, null);
+
+ currencyCode = rs.getString("currencyCode");
+ currencyDigits = JdbcSupport.getInteger(rs,
"currencyDigits");
+ inMultiplesOf = JdbcSupport.getInteger(rs,
"inMultiplesOf");
+ currency = new CurrencyData(currencyCode, currencyDigits,
inMultiplesOf);
+
+ final BigDecimal totalDeposits =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits");
+ final BigDecimal totalWithdrawals =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals");
+ final BigDecimal totalWithdrawalFees =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees");
+ final BigDecimal totalAnnualFees =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalAnnualFees");
+
+ final BigDecimal totalInterestEarned =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestEarned");
+ final BigDecimal totalInterestPosted =
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalInterestPosted");
+ final BigDecimal accountBalance =
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance");
+ final BigDecimal totalFeeCharge =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalFeeCharge");
+ final BigDecimal totalPenaltyCharge =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge");
+ final BigDecimal totalOverdraftInterestDerived =
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs,
+ "totalOverdraftInterestDerived");
+ final BigDecimal totalWithholdTax =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax");
+ final LocalDate interestPostedTillDate =
JdbcSupport.getLocalDate(rs, "interestPostedTillDate");
+
+ final BigDecimal minBalanceForInterestCalculation =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+ "minBalanceForInterestCalculation");
+ final BigDecimal onHoldFunds =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldFunds");
+
+ final BigDecimal onHoldAmount =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldAmount");
+
+ BigDecimal availableBalance = accountBalance;
+ if (availableBalance != null && onHoldFunds != null) {
+
+ availableBalance =
availableBalance.subtract(onHoldFunds);
+ }
+
+ if (availableBalance != null && onHoldAmount != null) {
+
+ availableBalance =
availableBalance.subtract(onHoldAmount);
+ }
+
+ BigDecimal interestNotPosted = BigDecimal.ZERO;
+ LocalDate lastInterestCalculationDate = null;
+ if (totalInterestEarned != null) {
+ interestNotPosted =
totalInterestEarned.subtract(totalInterestPosted).add(totalOverdraftInterestDerived);
+ lastInterestCalculationDate =
JdbcSupport.getLocalDate(rs, "lastInterestCalculationDate");
+ }
+
+ final SavingsAccountSummaryData summary = new
SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals,
+ totalWithdrawalFees, totalAnnualFees,
totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge,
+ totalPenaltyCharge, totalOverdraftInterestDerived,
totalWithholdTax, interestNotPosted,
+ lastInterestCalculationDate, availableBalance,
interestPostedTillDate);
+
summary.setPrevInterestPostedTillDate(interestPostedTillDate);
+
+ final boolean withHoldTax = rs.getBoolean("withHoldTax");
+ final Long taxGroupId =
JdbcSupport.getLongDefaultToNullIfZero(rs, "taxGroupId");
+ TaxGroupData taxGroupData = null;
+ if (taxGroupId != null) {
+ taxGroupData = TaxGroupData.lookup(taxGroupId, null);
+ }
+
+ final BigDecimal nominalAnnualInterestRate =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+ "nominalAnnualInterestRate");
+
+ final EnumOptionData interestCompoundingPeriodType =
SavingsEnumerations.compoundingInterestPeriodType(
+
SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs,
"interestCompoundingPeriodType")));
+
+ final EnumOptionData interestPostingPeriodType =
SavingsEnumerations.interestPostingPeriodType(
+
SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs,
"interestPostingPeriodType")));
+
+ final EnumOptionData interestCalculationType =
SavingsEnumerations.interestCalculationType(
+
SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs,
"interestCalculationType")));
+
+ final EnumOptionData interestCalculationDaysInYearType =
SavingsEnumerations
+
.interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType
+ .fromInt(JdbcSupport.getInteger(rs,
"interestCalculationDaysInYearType")));
+
+ final BigDecimal minRequiredOpeningBalance =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+ "minRequiredOpeningBalance");
+
+ final Integer lockinPeriodFrequency =
JdbcSupport.getInteger(rs, "lockinPeriodFrequency");
+ EnumOptionData lockinPeriodFrequencyType = null;
+ final Integer lockinPeriodFrequencyTypeValue =
JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
+ if (lockinPeriodFrequencyTypeValue != null) {
+ final SavingsPeriodFrequencyType lockinPeriodType =
SavingsPeriodFrequencyType
+ .fromInt(lockinPeriodFrequencyTypeValue);
+ lockinPeriodFrequencyType =
SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType);
+ }
+
+ final boolean withdrawalFeeForTransfers =
rs.getBoolean("withdrawalFeeForTransfers");
+
+ final boolean allowOverdraft =
rs.getBoolean("allowOverdraft");
+ final BigDecimal overdraftLimit =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit");
+ final BigDecimal nominalAnnualInterestRateOverdraft =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+ "nominalAnnualInterestRateOverdraft");
+ final BigDecimal minOverdraftForInterestCalculation =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+ "minOverdraftForInterestCalculation");
+
+ final BigDecimal minRequiredBalance =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance");
+ final boolean enforceMinRequiredBalance =
rs.getBoolean("enforceMinRequiredBalance");
+ savingsAccountData = SavingsAccountData.instance(id,
accountNo, depositType, externalId, null, null, null, null,
+ productId, null, null, null, status, subStatus,
timeline, currency, nominalAnnualInterestRate,
+ interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType,
+ interestCalculationDaysInYearType,
minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
+ withdrawalFeeForTransfers, summary,
allowOverdraft, overdraftLimit, minRequiredBalance,
+ enforceMinRequiredBalance,
minBalanceForInterestCalculation, onHoldFunds,
nominalAnnualInterestRateOverdraft,
+ minOverdraftForInterestCalculation, withHoldTax,
taxGroupData, lastActiveTransactionDate,
+ isDormancyTrackingActive, daysToInactive,
daysToDormancy, daysToEscheat, onHoldAmount);
+
+ savingsAccountData.setClientData(clientData);
+ savingsAccountData.setGroupGeneralData(groupGeneralData);
+ savingsAccountData.setSavingsProduct(savingsProductData);
+
savingsAccountData.setGlAccountIdForInterestOnSavings(glAccountIdForInterestOnSavings);
+
savingsAccountData.setGlAccountIdForSavingsControl(glAccountIdForSavingsControl);
+ }
+
+ if (!transMap.containsValue(transactionId)) {
+
+ final int transactionTypeInt = JdbcSupport.getInteger(rs,
"transactionType");
+ final SavingsAccountTransactionEnumData transactionType =
SavingsEnumerations.transactionType(transactionTypeInt);
+
+ final LocalDate date = JdbcSupport.getLocalDate(rs,
"transactionDate");
+ final LocalDate balanceEndDate =
JdbcSupport.getLocalDate(rs, "balanceEndDate");
+ final LocalDate transSubmittedOnDate =
JdbcSupport.getLocalDate(rs, "createdDate");
+ final BigDecimal amount =
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount");
+ final BigDecimal overdraftAmount =
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "overdraftAmount");
+ final BigDecimal outstandingChargeAmount = null;
+ final BigDecimal runningBalance =
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "runningBalance");
+ final boolean reversed = rs.getBoolean("reversed");
+ final boolean isManualTransaction =
rs.getBoolean("manualTransaction");
+ final Long officeId = rs.getLong("officeId");
+ final BigDecimal cumulativeBalance =
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "cumulativeBalance");
+
+ final boolean postInterestAsOn = false;
+
+ PaymentDetailData paymentDetailData = null;
+ if (transactionType.isDepositOrWithdrawal()) {
+ final Long paymentTypeId = JdbcSupport.getLong(rs,
"paymentType");
+ if (paymentTypeId != null) {
+ final String typeName =
rs.getString("paymentTypeName");
+ final PaymentTypeData paymentTypeData = new
PaymentTypeData(paymentTypeId, typeName, null, false, null);
+ paymentDetailData = new PaymentDetailData(id,
paymentTypeData, null, null, null, null, null);
+ }
+ }
+
+ savingsAccountTransactionData =
SavingsAccountTransactionData.create(transactionId, transactionType,
paymentDetailData,
+ id, accountNo, date, currency, amount,
outstandingChargeAmount, runningBalance, reversed, transSubmittedOnDate,
+ postInterestAsOn, cumulativeBalance,
balanceEndDate);
+
savingsAccountTransactionData.setOverdraftAmount(overdraftAmount);
+
+ transMap.put("id", transactionId);
+ if (savingsAccountData.getOfficeId() == null) {
+ savingsAccountData.setOfficeId(officeId);
+ }
+
+
savingsAccountData.setSavingsAccountTransactionData(savingsAccountTransactionData);
+ }
+
+ if (chargeId != null &&
!chargeDetails.containsValue(chargeId)) {
+ final boolean isPenalty = rs.getBoolean("isPenaltyCharge");
+ final BigDecimal chargeAmount =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "chargeAmount");
+ final Integer chargesTimeType =
rs.getInt("chargeTimeType");
+ final EnumOptionData enumOptionDataForChargesTimeType =
new EnumOptionData(chargesTimeType.longValue(), null, null);
+ final SavingsAccountChargeData savingsAccountChargeData =
new SavingsAccountChargeData(chargeId, chargeAmount,
+ enumOptionDataForChargesTimeType, isPenalty);
+
+ final Long chargesPaidById = rs.getLong("chargesPaidById");
+ final BigDecimal chargesPaid =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "paidByAmount");
+ final SavingsAccountChargesPaidByData
savingsAccountChargesPaidByData = new SavingsAccountChargesPaidByData(
+ chargesPaidById, chargesPaid);
+
savingsAccountChargesPaidByData.setSavingsAccountChargeData(savingsAccountChargeData);
+ if (savingsAccountChargesPaidByData != null) {
+
savingsAccountTransactionData.setChargesPaidByData(savingsAccountChargesPaidByData);
+ }
+
+ chargeDetails.put("id", chargeId);
+ }
+
+ if (taxDetailId != null &&
!taxDetails.containsValue(taxDetailId)) {
+ final BigDecimal amount =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "taxAmount");
+ final BigDecimal percentage =
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "taxPercentage");
+ final Long debitId = rs.getLong("debitAccountId");
+ final Long creditId = rs.getLong("creditAccountId");
+ final GLAccountData debitAccount =
GLAccountData.createFrom(debitId);
+ final GLAccountData creditAccount =
GLAccountData.createFrom(creditId);
+
+ if (taxComponentId != null) {
+ final TaxComponentData taxComponent =
TaxComponentData.createTaxComponent(taxComponentId, percentage, debitAccount,
+ creditAccount);
+ savingsAccountTransactionData.setTaxDetails(new
TaxDetailsData(taxComponent, amount));
+ }
+
+ taxDetails.put("id", taxDetailId);
+ }
+
+ }
+ if (savingsAccountData != null) {
+ savingsAccountDataList.add(savingsAccountData);
+ }
+ return savingsAccountDataList;
+
+ // final String productName = rs.getString("productName");
Review comment:
If we need to keep the native SQL approach, we shall probably fix the
comments before merging.
##########
File path:
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java
##########
@@ -87,6 +88,12 @@ void processPostActiveActions(SavingsAccount account,
DateTimeFormatter fmt, Set
void postInterest(SavingsAccount account, boolean postInterestAs,
LocalDate transactionDate, boolean backdatedTxnsAllowedTill);
+ // SavingsAccountData postInterest(SavingsAccountData account, boolean
postInterestAs, LocalDate transactionDate,
+ // boolean backdatedTxnsAllowedTill);
Review comment:
Do we need these commented lines? Thanks!
##########
File path:
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionDataComparator.java
##########
@@ -0,0 +1,44 @@
+/**
+ * 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.portfolio.savings.domain;
+
+import java.util.Comparator;
+import
org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+
+public class SavingsAccountTransactionDataComparator implements
Comparator<SavingsAccountTransactionData> {
+
+ @Override
+ public int compare(final SavingsAccountTransactionData o1, final
SavingsAccountTransactionData o2) {
+ int compareResult = 0;
+
+ final int comparsion =
o1.getTransactionDate().compareTo(o2.getLastTransactionDate());
+ if (comparsion == 0) {
+ compareResult =
o1.getSubmittedOnDate().compareTo(o2.getSubmittedOnDate());
+ if (compareResult == 0 && o1.getId() != null && o2.getId() !=
null) {
+ compareResult = o1.getId().compareTo(o2.getId());
+ } else {
+ compareResult = comparsion;
+ }
+ } else {
+ compareResult = comparsion;
+ }
+ return compareResult;
+ }
Review comment:
Do you think we could replace this whole thing with something like:
`Comparator.comparing(SavingsAccountTransactionData::getTransactionDate).thenComparing(SavingsAccountTransactionData::getSubmittedOnDate).thenComparing(SavingsAccountTransactionData::getId);`
##########
File path:
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java
##########
@@ -77,38 +108,61 @@ public void setTenant(FineractPlatformTenant tenant) {
this.tenant = tenant;
}
+ public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ public void setTransactionTemplate(TransactionTemplate
transactionTemplate) {
+ this.transactionTemplate = transactionTemplate;
+ }
+
+ public void setResolutionHelper(ResolutionHelper resolutionHelper) {
+ this.resolutionHelper = resolutionHelper;
+ }
+
+ public void setStrategyProvider(CommandStrategyProvider
commandStrategyProvider) {
+ this.strategyProvider = commandStrategyProvider;
+ }
+
@Override
@SuppressFBWarnings(value = {
"DMI_RANDOM_USED_ONLY_ONCE" }, justification = "False positive for
random object created and used only once")
public Void call() throws
org.apache.fineract.infrastructure.jobs.exception.JobExecutionException {
ThreadLocalContextUtil.setTenant(tenant);
Integer maxNumberOfRetries =
tenant.getConnection().getMaxRetriesOnDeadlock();
Integer maxIntervalBetweenRetries =
tenant.getConnection().getMaxIntervalBetweenRetries();
- final boolean backdatedTxnsAllowedTill =
this.configurationDomainService.retrievePivotDateConfig();
+
+ // List<BatchResponse> responseList = new ArrayList<>();
Review comment:
I assume this was left here accidentally.
##########
File path:
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
##########
@@ -156,6 +160,8 @@
private final StandingInstructionRepository standingInstructionRepository;
private final BusinessEventNotifierService businessEventNotifierService;
private final GSIMRepositoy gsimRepository;
+ private final JdbcTemplate jdbcTemplate;
Review comment:
I guess we don't need the JdbcTemplate in this class (?)
##########
File path:
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
##########
@@ -230,6 +246,495 @@ public SavingsAccountData retrieveOne(final Long
accountId) {
}
}
+ @Override
+ public List<SavingsAccountTransactionData>
retrieveAllTransactionData(final List<String> refNo) throws DataAccessException
{
+ String inSql = String.join(",", Collections.nCopies(refNo.size(),
"?"));
+ String sql = "select " +
this.savingsAccountTransactionsForBatchMapper.schema() + " where tr.ref_no in
(%s)";
+ Object[] params = new Object[refNo.size()];
+ int i = 0;
+ for (String element : refNo) {
+ params[i] = element;
+ i++;
+ }
+ return this.jdbcTemplate.query(String.format(sql, inSql),
this.savingsAccountTransactionsForBatchMapper, params);
+ }
+
+ @Override
+ public List<SavingsAccountData>
retrieveAllSavingsDataForInterestPosting(final boolean
backdatedTxnsAllowedTill, final int pageSize,
+ final Integer status, final Long maxSavingsId) {
+ LocalDate currentDate = DateUtils.getLocalDateOfTenant().minusDays(1);
+
+ String sql = "select " +
this.savingAccountMapperForInterestPosting.schema()
+ + "join (select a.id from m_savings_account a where a.id > ?
and a.status_enum = ? limit ?) b on b.id = sa.id ";
+ if (backdatedTxnsAllowedTill) {
+ sql = sql
+ + "where if (sa.interest_posted_till_date is not null,
tr.transaction_date >= sa.interest_posted_till_date, tr.transaction_date >=
sa.activatedon_date) ";
+ }
+
+ sql = sql + "and apm.product_type=2 and sa.interest_posted_till_date<"
+ java.sql.Date.valueOf(currentDate);
+ sql = sql + " order by sa.id, tr.transaction_date, tr.created_date,
tr.id";
+
+ List<SavingsAccountData> savingsAccountDataList =
this.jdbcTemplate.query(sql, this.savingAccountMapperForInterestPosting,
+ new Object[] { maxSavingsId, status, pageSize });
+ for (SavingsAccountData savingsAccountData : savingsAccountDataList) {
+ this.savingAccountAssembler.assembleSavings(savingsAccountData);
+ }
+ return savingsAccountDataList;
+ }
+
+ private static final class SavingAccountMapperForInterestPosting
implements ResultSetExtractor<List<SavingsAccountData>> {
+
+ private final String schemaSql;
+
+ SavingAccountMapperForInterestPosting() {
+ final StringBuilder sqlBuilder = new StringBuilder(400);
+ sqlBuilder.append("sa.id as id, sa.account_no as accountNo,
sa.external_id as externalId, ");
+ sqlBuilder.append("sa.deposit_type_enum as depositType, ");
+ sqlBuilder.append("c.id as clientId, c.office_id as
clientOfficeId, ");
+ sqlBuilder.append("g.id as groupId, g.office_id as groupOfficeId,
");
Review comment:
Probably this won't be needed if we can switch the query above to JPQL.
##########
File path:
fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java
##########
@@ -0,0 +1,564 @@
+/**
+ * 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.portfolio.savings.service;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+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.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import
org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import
org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
+import
org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+import
org.apache.fineract.portfolio.savings.domain.SavingsAccountChargesPaidByData;
+import
org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionDataComparator;
+import org.apache.fineract.portfolio.savings.domain.SavingsHelper;
+import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
+import org.apache.fineract.portfolio.tax.data.TaxComponentData;
+import org.apache.fineract.portfolio.tax.service.TaxUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SavingsAccountInterestPostingServiceImpl implements
SavingsAccountInterestPostingService {
+
+ private final SavingsHelper savingsHelper;
+
+ @Autowired
+ public SavingsAccountInterestPostingServiceImpl(final SavingsHelper
savingsHelper) {
+ this.savingsHelper = savingsHelper;
+ }
+
+ @Override
+ public SavingsAccountData postInterest(final MathContext mc, final
LocalDate interestPostingUpToDate, final boolean isInterestTransfer,
+ final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final
Integer financialYearBeginningMonth,
+ final LocalDate postInterestOnDate, final boolean
backdatedTxnsAllowedTill, final SavingsAccountData savingsAccountData) {
+
+ Money interestPostedToDate = Money.zero(savingsAccountData.currency());
+ LocalDate startInterestDate =
getStartInterestCalculationDate(savingsAccountData);
+
+ if (backdatedTxnsAllowedTill &&
savingsAccountData.getSummary().getInterestPostedTillDate() != null) {
+ interestPostedToDate = Money.of(savingsAccountData.currency(),
savingsAccountData.getSummary().getTotalInterestPosted());
+ SavingsAccountTransactionData savingsAccountTransactionData =
retrieveLastTransactions(savingsAccountData);
+ Date lastTransactionDate = Date.from(
+
savingsAccountTransactionData.getLastTransactionDate().atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant());
+
savingsAccountData.setStartInterestCalculationDate(lastTransactionDate);
+ } else {
+ savingsAccountData.setStartInterestCalculationDate(
+
Date.from(startInterestDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()));
+ }
+
+ final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc,
interestPostingUpToDate, isInterestTransfer,
+ isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill,
+ savingsAccountData);
+
+ boolean recalucateDailyBalanceDetails = false;
+ boolean applyWithHoldTax =
isWithHoldTaxApplicableForInterestPosting(savingsAccountData);
+ final List<SavingsAccountTransactionData> withholdTransactions = new
ArrayList<>();
+
+
withholdTransactions.addAll(findWithHoldSavingsTransactionsWithPivotConfig(savingsAccountData));
+
+ for (final PostingPeriod interestPostingPeriod : postingPeriods) {
+
+ final LocalDate interestPostingTransactionDate =
interestPostingPeriod.dateOfPostingTransaction();
+ final Money interestEarnedToBePostedForPeriod =
interestPostingPeriod.getInterestEarned();
+
+ if
(!interestPostingTransactionDate.isAfter(interestPostingUpToDate)) {
+ interestPostedToDate =
interestPostedToDate.plus(interestEarnedToBePostedForPeriod);
+
+ final SavingsAccountTransactionData postingTransaction =
findInterestPostingTransactionFor(interestPostingTransactionDate,
+ savingsAccountData);
+
+ if (postingTransaction == null) {
+ SavingsAccountTransactionData newPostingTransaction;
+ if
(interestEarnedToBePostedForPeriod.isGreaterThanOrEqualTo(Money.zero(savingsAccountData.currency())))
{
+ newPostingTransaction =
SavingsAccountTransactionData.interestPosting(savingsAccountData,
+ interestPostingTransactionDate,
interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting());
+ } else {
+ newPostingTransaction =
SavingsAccountTransactionData.interestPosting(savingsAccountData,
+ interestPostingTransactionDate,
interestEarnedToBePostedForPeriod.negated(),
+ interestPostingPeriod.isUserPosting());
+ }
+
+
savingsAccountData.updateTransactions(newPostingTransaction);
+
+ if (applyWithHoldTax) {
+
createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(),
interestPostingTransactionDate,
+ savingsAccountData);
+ }
+ recalucateDailyBalanceDetails = true;
+ } else {
+ boolean correctionRequired = false;
+ if (postingTransaction.isInterestPostingAndNotReversed()) {
+ correctionRequired =
postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod);
+ } else {
+ correctionRequired =
postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod.negated());
+ }
+ if (correctionRequired) {
+ boolean applyWithHoldTaxForOldTransaction = false;
+ postingTransaction.reverse();
+
+ final SavingsAccountTransactionData
withholdTransaction = findTransactionFor(interestPostingTransactionDate,
+ withholdTransactions);
+
+ if (withholdTransaction != null) {
+ withholdTransaction.reverse();
+ applyWithHoldTaxForOldTransaction = true;
+ }
+ SavingsAccountTransactionData newPostingTransaction;
+ if
(interestEarnedToBePostedForPeriod.isGreaterThanOrEqualTo(Money.zero(savingsAccountData.currency())))
{
+ newPostingTransaction =
SavingsAccountTransactionData.interestPosting(savingsAccountData,
+ interestPostingTransactionDate,
interestEarnedToBePostedForPeriod,
+ interestPostingPeriod.isUserPosting());
+ } else {
+ newPostingTransaction =
SavingsAccountTransactionData.overdraftInterest(savingsAccountData,
+ interestPostingTransactionDate,
interestEarnedToBePostedForPeriod.negated(),
+ interestPostingPeriod.isUserPosting());
+ }
+
+
savingsAccountData.updateTransactions(newPostingTransaction);
+
+ if (applyWithHoldTaxForOldTransaction) {
+
createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(),
interestPostingTransactionDate,
+ savingsAccountData);
+ }
+ recalucateDailyBalanceDetails = true;
+ }
+ }
+ }
+ }
+
+ if (recalucateDailyBalanceDetails) {
+ // no openingBalance concept supported yet but probably will to
+ // allow
+ // for migrations.
+ Money openingAccountBalance =
Money.zero(savingsAccountData.currency());
+
+ if (backdatedTxnsAllowedTill) {
+ if
(savingsAccountData.getSummary().getLastInterestCalculationDate() == null) {
+ openingAccountBalance =
Money.zero(savingsAccountData.currency());
+ } else {
+ openingAccountBalance =
Money.of(savingsAccountData.currency(),
+
savingsAccountData.getSummary().getRunningBalanceOnPivotDate());
+ }
+ }
+
+ // update existing transactions so derived balance fields are
+ // correct.
+ recalculateDailyBalances(openingAccountBalance,
interestPostingUpToDate, backdatedTxnsAllowedTill, savingsAccountData);
+ }
+
+ if (!backdatedTxnsAllowedTill) {
+
savingsAccountData.getSummary().updateSummary(savingsAccountData.currency(),
+
savingsAccountData.getSavingsAccountTransactionSummaryWrapper(),
savingsAccountData.getSavingsAccountTransactionData());
+ } else {
+
savingsAccountData.getSummary().updateSummaryWithPivotConfig(savingsAccountData.currency(),
+
savingsAccountData.getSavingsAccountTransactionSummaryWrapper(), null,
+ savingsAccountData.getSavingsAccountTransactionData());
+ }
+
+ return savingsAccountData;
+ }
+
+ protected SavingsAccountTransactionData findTransactionFor(final LocalDate
postingDate,
+ final List<SavingsAccountTransactionData> transactions) {
+ SavingsAccountTransactionData transaction = null;
+ for (final SavingsAccountTransactionData savingsAccountTransaction :
transactions) {
+ if (savingsAccountTransaction.occursOn(postingDate)) {
+ transaction = savingsAccountTransaction;
+ break;
+ }
+ }
+ return transaction;
+ }
+
+ public List<PostingPeriod> calculateInterestUsing(final MathContext mc,
final LocalDate upToInterestCalculationDate,
+ boolean isInterestTransfer, final boolean
isSavingsInterestPostingAtCurrentPeriodEnd, final Integer
financialYearBeginningMonth,
+ final LocalDate postInterestOnDate, final boolean
backdatedTxnsAllowedTill, final SavingsAccountData savingsAccountData) {
+
+ // no openingBalance concept supported yet but probably will to allow
+ // for migrations.
+ Money openingAccountBalance = null;
+
+ // Check global configurations and 'pivot' date is null
+ if (backdatedTxnsAllowedTill) {
+ openingAccountBalance = Money.of(savingsAccountData.currency(),
savingsAccountData.getSummary().getRunningBalanceOnPivotDate());
+ } else {
+ openingAccountBalance = Money.zero(savingsAccountData.currency());
+ }
+
+ // update existing transactions so derived balance fields are
+ // correct.
+ recalculateDailyBalances(openingAccountBalance,
upToInterestCalculationDate, backdatedTxnsAllowedTill, savingsAccountData);
+
+ // 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(savingsAccountData.getInterestPostingPeriodType());
+
+ final SavingsCompoundingInterestPeriodType compoundingPeriodType =
SavingsCompoundingInterestPeriodType
+
.fromInt(savingsAccountData.getInterestCompoundingPeriodType());
+
+ final SavingsInterestCalculationDaysInYearType daysInYearType =
SavingsInterestCalculationDaysInYearType
+
.fromInt(savingsAccountData.getInterestCalculationDaysInYearType());
+
+ List<LocalDate> postedAsOnDates =
getManualPostingDates(savingsAccountData);
+ if (postInterestOnDate != null) {
+ postedAsOnDates.add(postInterestOnDate);
+ }
+ final List<LocalDateInterval> postingPeriodIntervals =
this.savingsHelper.determineInterestPostingPeriods(
+ savingsAccountData.getStartInterestCalculationDate(),
upToInterestCalculationDate, postingPeriodType,
+ financialYearBeginningMonth, postedAsOnDates);
+
+ final List<PostingPeriod> allPostingPeriods = new ArrayList<>();
+
+ Money periodStartingBalance;
+ if (savingsAccountData.getStartInterestCalculationDate() != null) {
+ final SavingsAccountTransactionData transaction =
retrieveLastTransactions(savingsAccountData);
+
+ if (transaction == null) {
+ final String defaultUserMessage = "No transactions were found
on the specified date "
+ +
savingsAccountData.getStartInterestCalculationDate().toString() + " for account
number "
+ + savingsAccountData.getAccountNo() + " and resource
id " + savingsAccountData.getId();
+
+ final ApiParameterError error =
ApiParameterError.parameterError(
+
"error.msg.savingsaccount.transaction.incorrect.start.interest.calculation.date",
defaultUserMessage,
+ "transactionDate",
savingsAccountData.getStartInterestCalculationDate().toString());
+
+ final List<ApiParameterError> dataValidationErrors = new
ArrayList<>();
+ dataValidationErrors.add(error);
+
+ throw new
PlatformApiDataValidationException(dataValidationErrors);
+ }
+
+ periodStartingBalance =
transaction.getRunningBalance(savingsAccountData.currency());
+ } else {
+ periodStartingBalance = Money.zero(savingsAccountData.currency());
+ }
+
+ final SavingsInterestCalculationType interestCalculationType =
SavingsInterestCalculationType
+ .fromInt(savingsAccountData.getInterestCalculationType());
+ final BigDecimal interestRateAsFraction =
getEffectiveInterestRateAsFraction(mc, upToInterestCalculationDate,
savingsAccountData);
+ final BigDecimal overdraftInterestRateAsFraction =
getEffectiveOverdraftInterestRateAsFraction(mc, savingsAccountData);
+ final Collection<Long> interestPostTransactions =
this.savingsHelper.fetchPostInterestTransactionIds(savingsAccountData.getId());
+ final Money minBalanceForInterestCalculation =
Money.of(savingsAccountData.currency(),
+ minBalanceForInterestCalculation(savingsAccountData));
+ final Money minOverdraftForInterestCalculation =
Money.of(savingsAccountData.currency(),
+ savingsAccountData.getMinOverdraftForInterestCalculation());
+ final MonetaryCurrency monetaryCurrency =
MonetaryCurrency.fromCurrencyData(savingsAccountData.currency());
+
+ for (final LocalDateInterval periodInterval : postingPeriodIntervals) {
+
+ boolean isUserPosting = false;
+ if
(postedAsOnDates.contains(periodInterval.endDate().plusDays(1))) {
+ isUserPosting = true;
+ }
+ final PostingPeriod postingPeriod =
PostingPeriod.createFromDTO(periodInterval, periodStartingBalance,
+
retreiveOrderedNonInterestPostingTransactions(savingsAccountData),
monetaryCurrency, compoundingPeriodType,
+ interestCalculationType, interestRateAsFraction,
daysInYearType.getValue(), upToInterestCalculationDate,
+ interestPostTransactions, isInterestTransfer,
minBalanceForInterestCalculation,
+ isSavingsInterestPostingAtCurrentPeriodEnd,
overdraftInterestRateAsFraction, minOverdraftForInterestCalculation,
+ isUserPosting, financialYearBeginningMonth);
+
+ periodStartingBalance = postingPeriod.closingBalance();
+
+ allPostingPeriods.add(postingPeriod);
+ }
+
+
this.savingsHelper.calculateInterestForAllPostingPeriods(monetaryCurrency,
allPostingPeriods,
+ getLockedInUntilLocalDate(savingsAccountData), false);
+
+
savingsAccountData.getSummary().updateFromInterestPeriodSummaries(monetaryCurrency,
allPostingPeriods);
+
+ if (backdatedTxnsAllowedTill) {
+
savingsAccountData.getSummary().updateSummaryWithPivotConfig(savingsAccountData.currency(),
+
savingsAccountData.getSavingsAccountTransactionSummaryWrapper(), null,
savingsAccountData.getTransactions());
+ } else {
+
savingsAccountData.getSummary().updateSummary(savingsAccountData.currency(),
+
savingsAccountData.getSavingsAccountTransactionSummaryWrapper(),
savingsAccountData.getTransactions());
+ }
+
+ return allPostingPeriods;
+ }
+
+ private List<SavingsAccountTransactionData>
retreiveOrderedNonInterestPostingTransactions(final SavingsAccountData
savingsAccountData) {
+ final List<SavingsAccountTransactionData> listOfTransactionsSorted =
retreiveListOfTransactions(savingsAccountData);
+
+ final List<SavingsAccountTransactionData>
orderedNonInterestPostingTransactions = new ArrayList<>();
+
+ for (final SavingsAccountTransactionData transaction :
listOfTransactionsSorted) {
+ if (!(transaction.isInterestPostingAndNotReversed() ||
transaction.isOverdraftInterestAndNotReversed())
+ && transaction.isNotReversed()) {
+ orderedNonInterestPostingTransactions.add(transaction);
+ }
+ }
+ orderedNonInterestPostingTransactions.sort(new
SavingsAccountTransactionDataComparator());
+ return orderedNonInterestPostingTransactions;
+ }
+
+ private List<SavingsAccountTransactionData>
retreiveListOfTransactions(final SavingsAccountData savingsAccountData) {
+ if (savingsAccountData.getTransactions() != null &&
savingsAccountData.getTransactions().size() == 1) {
+ return savingsAccountData.getTransactions();
+ }
+
+ final List<SavingsAccountTransactionData> listOfTransactionsSorted =
new ArrayList<>();
+ listOfTransactionsSorted.addAll(savingsAccountData.getTransactions());
+
+ final SavingsAccountTransactionDataComparator transactionComparator =
new SavingsAccountTransactionDataComparator();
+ Collections.sort(listOfTransactionsSorted, transactionComparator);
+ return listOfTransactionsSorted;
+ }
+
+ protected LocalDate getLockedInUntilLocalDate(final SavingsAccountData
savingsAccount) {
+ LocalDate lockedInUntilLocalDate = null;
+ if (savingsAccount.getLockedInUntilDate() != null) {
+ lockedInUntilLocalDate = savingsAccount.getActivationLocalDate();
+ // lockedInUntilLocalDate =
LocalDate.ofInstant(this.lockedInUntilDate.toInstant(),
+ // DateUtils.getDateTimeZoneOfTenant());
+ }
+ return lockedInUntilLocalDate;
+ }
+
+ private BigDecimal minBalanceForInterestCalculation(final
SavingsAccountData savingsAccountData) {
+ return savingsAccountData.getMinBalanceForInterestCalculation();
+ }
+
+ private BigDecimal getEffectiveOverdraftInterestRateAsFraction(MathContext
mc, final SavingsAccountData savingsAccountData) {
+ return
savingsAccountData.getNominalAnnualInterestRateOverdraft().divide(BigDecimal.valueOf(100L),
mc);
+ }
+
+ @SuppressWarnings("unused")
+ private BigDecimal getEffectiveInterestRateAsFraction(final MathContext
mc, final LocalDate upToInterestCalculationDate,
+ final SavingsAccountData savingsAccountData) {
+ return
savingsAccountData.getNominalAnnualInterestRate().divide(BigDecimal.valueOf(100L),
mc);
+ }
+
+ public List<SavingsAccountTransactionData> getTransactions(final
SavingsAccountData savingsAccountData) {
+ return savingsAccountData.getTransactions();
+ }
+
+ private SavingsAccountTransactionData retrieveLastTransactions(final
SavingsAccountData savingsAccountData) {
+ if (savingsAccountData.getTransactions() != null &&
savingsAccountData.getTransactions().size() == 1) {
+ return savingsAccountData.getTransactions().get(0);
+ }
+ final List<SavingsAccountTransactionData> listOfTransactionsSorted =
new ArrayList<>();
+ listOfTransactionsSorted.addAll(savingsAccountData.getTransactions());
+ final SavingsAccountTransactionDataComparator transactionComparator =
new SavingsAccountTransactionDataComparator();
+ Collections.sort(listOfTransactionsSorted, transactionComparator);
+ return listOfTransactionsSorted.get(0);
+ }
+
+ public LocalDate getStartInterestCalculationDate(final SavingsAccountData
savingsAccountData) {
+ LocalDate startInterestCalculationLocalDate = null;
+ if (savingsAccountData.getStartInterestCalculationDate() != null) {
+ startInterestCalculationLocalDate =
savingsAccountData.getStartInterestCalculationDate();
+ } else {
+ startInterestCalculationLocalDate =
getActivationLocalDate(savingsAccountData);
+ }
+ return startInterestCalculationLocalDate;
+ }
+
+ public LocalDate getActivationLocalDate(final SavingsAccountData
savingsAccountData) {
+ LocalDate activationLocalDate = null;
+ if (savingsAccountData.getActivationLocalDate() != null) {
+ activationLocalDate = savingsAccountData.getActivationLocalDate();
+ }
+ return activationLocalDate;
+ }
+
+ public List<LocalDate> getManualPostingDates(final SavingsAccountData
savingsAccountData) {
+ List<LocalDate> transactions = new ArrayList<>();
+ for (SavingsAccountTransactionData trans :
savingsAccountData.getSavingsAccountTransactionData()) {
+ if (trans.isInterestPosting() && trans.isNotReversed() &&
trans.isManualTransaction()) {
+ transactions.add(trans.getTransactionLocalDate());
+ }
+ }
+ return transactions;
+ }
+
+ protected void recalculateDailyBalances(final Money openingAccountBalance,
final LocalDate interestPostingUpToDate,
+ final boolean backdatedTxnsAllowedTill, final SavingsAccountData
savingsAccountData) {
+
+ Money runningBalance = openingAccountBalance.copy();
+
+ List<SavingsAccountTransactionData> accountTransactionsSorted =
retreiveListOfTransactions(savingsAccountData);
+
+ boolean isTransactionsModified = false;
+
+ for (final SavingsAccountTransactionData transaction :
accountTransactionsSorted) {
+ if (transaction.isReversed()) {
+ transaction.zeroBalanceFields();
+ } else {
+ Money overdraftAmount =
Money.zero(savingsAccountData.currency());
+ Money transactionAmount =
Money.zero(savingsAccountData.currency());
+ if (transaction.isCredit() || transaction.isAmountRelease()) {
+ if (runningBalance.isLessThanZero()) {
+ Money diffAmount =
Money.of(savingsAccountData.currency(),
transaction.getAmount()).plus(runningBalance);
+ if (diffAmount.isGreaterThanZero()) {
+ overdraftAmount =
Money.of(savingsAccountData.currency(),
transaction.getAmount()).minus(diffAmount);
+ } else {
+ overdraftAmount =
Money.of(savingsAccountData.currency(), transaction.getAmount());
+ }
+ }
+ transactionAmount =
transactionAmount.plus(transaction.getAmount());
+ } else if (transaction.isDebit() ||
transaction.isAmountOnHold()) {
+ if (runningBalance.isLessThanZero()) {
+ overdraftAmount =
Money.of(savingsAccountData.currency(), transaction.getAmount());
+ }
+ transactionAmount =
transactionAmount.minus(transaction.getAmount());
+ }
+
+ runningBalance = runningBalance.plus(transactionAmount);
+ transaction.updateRunningBalance(runningBalance);
+ if (overdraftAmount.isZero() &&
runningBalance.isLessThanZero()) {
+ overdraftAmount =
overdraftAmount.plus(runningBalance.getAmount().negate());
+ }
+ if (transaction.getId() == null &&
overdraftAmount.isGreaterThanZero()) {
+
transaction.updateOverdraftAmount(overdraftAmount.getAmount());
+ } else if
(overdraftAmount.isNotEqualTo(Money.of(savingsAccountData.currency(),
transaction.getOverdraftAmount()))) {
+ SavingsAccountTransactionData accountTransaction =
SavingsAccountTransactionData.copyTransaction(transaction);
+ if (transaction.isChargeTransaction()) {
+ Set<SavingsAccountChargesPaidByData> chargesPaidBy =
transaction.getSavingsAccountChargesPaid();
+ final Set<SavingsAccountChargesPaidByData>
newChargePaidBy = new HashSet<>();
+ chargesPaidBy.forEach(
+ x ->
newChargePaidBy.add(SavingsAccountChargesPaidByData.instance(x.getChargeId(),
x.getAmount())));
+
accountTransaction.getSavingsAccountChargesPaid().addAll(newChargePaidBy);
+ }
+ transaction.reverse();
+ if (overdraftAmount.isGreaterThanZero()) {
+
accountTransaction.updateOverdraftAmount(overdraftAmount.getAmount());
+ }
+ accountTransaction.updateRunningBalance(runningBalance);
+ addTransactionToExisting(accountTransaction,
savingsAccountData);
+
+ isTransactionsModified = true;
+ }
+
+ }
+ }
+
+ if (isTransactionsModified) {
+ accountTransactionsSorted =
retreiveListOfTransactions(savingsAccountData);
+ }
+ resetAccountTransactionsEndOfDayBalances(accountTransactionsSorted,
interestPostingUpToDate, savingsAccountData);
+ }
+
+ public void addTransactionToExisting(final SavingsAccountTransactionData
transaction, final SavingsAccountData savingsAccountData) {
+ savingsAccountData.updateTransactions(transaction);
+ }
+
+ private List<SavingsAccountTransactionData>
findWithHoldSavingsTransactionsWithPivotConfig(
+ final SavingsAccountData savingsAccountData) {
+ final List<SavingsAccountTransactionData> withholdTransactions = new
ArrayList<>();
+ List<SavingsAccountTransactionData> trans =
savingsAccountData.getSavingsAccountTransactionData();
+ for (final SavingsAccountTransactionData transaction : trans) {
+ if (transaction.isWithHoldTaxAndNotReversed()) {
+ withholdTransactions.add(transaction);
+ }
+ }
+ return withholdTransactions;
+ }
+
+ private boolean createWithHoldTransaction(final BigDecimal amount, final
LocalDate date, final SavingsAccountData savingsAccountData) {
+ boolean isTaxAdded = false;
+ if (savingsAccountData.getTaxGroupData() != null &&
amount.compareTo(BigDecimal.ZERO) > 0) {
+ Map<TaxComponentData, BigDecimal> taxSplit =
TaxUtils.splitTaxData(amount, date,
+
savingsAccountData.getTaxGroupData().getTaxAssociations().stream().collect(Collectors.toSet()),
amount.scale());
+ BigDecimal totalTax = TaxUtils.totalTaxDataAmount(taxSplit);
+ if (totalTax.compareTo(BigDecimal.ZERO) > 0) {
+ SavingsAccountTransactionData withholdTransaction =
SavingsAccountTransactionData.withHoldTax(savingsAccountData, date,
+ Money.of(savingsAccountData.currency(), totalTax),
taxSplit);
+
+
savingsAccountData.getSavingsAccountTransactionData().add(withholdTransaction);
+ // if (backdatedTxnsAllowedTill) {
Review comment:
Probably we should fix this too.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]