diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java index ee76982f5..c2ffffe6d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java @@ -600,6 +600,27 @@ public class AccountingProcessorHelper { createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount); } + /** + * Creates a direct transfer journal entry between two savings-type accounts (savings or current), + * each with their own product GL mapping and entity ID so the correct account number appears per entry. + * + * @param debitProductId product ID whose SAVINGS_CONTROL GL is debited + * @param debitEntityId entity (account) ID shown on the debit entry + * @param creditProductId product ID whose SAVINGS_CONTROL GL is credited + * @param creditEntityId entity (account) ID shown on the credit entry + */ + public void createDirectTransferJournalEntriesForSavings(final Office office, final String currencyCode, + final Long debitProductId, final Long debitEntityId, + final Long creditProductId, final Long creditEntityId, + final String transactionId, final LocalDate transactionDate, final BigDecimal amount) { + final GLAccount debitAccount = getLinkedGLAccountForSavingsProduct(debitProductId, + CashAccountsForSavings.SAVINGS_CONTROL.getValue(), null); + final GLAccount creditAccount = getLinkedGLAccountForSavingsProduct(creditProductId, + CashAccountsForSavings.SAVINGS_CONTROL.getValue(), null); + createDebitJournalEntryForSavings(office, currencyCode, debitAccount, debitEntityId, transactionId, transactionDate, amount); + createCreditJournalEntryForSavings(office, currencyCode, creditAccount, creditEntityId, transactionId, transactionDate, amount); + } + /** * Convenience method that creates a pair of related Debits and Credits for Cash Based accounting. * diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java index 519edb532..66f577291 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java @@ -106,8 +106,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat .append(" curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ") .append(" eao.external_id as externalAssetOwner, ") .append(" CASE ") - .append(" WHEN journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id LIKE 'CA%' THEN ca.account_no ") - .append(" WHEN journalEntry.entity_type_enum = 2 THEN sa.account_no ") + .append(" WHEN journalEntry.entity_type_enum = 2 THEN COALESCE(ca.account_no, sa.account_no) ") .append(" ELSE NULL ") .append(" END as savingsAccountNo "); if (associationParametersData.isRunningBalanceRequired()) { @@ -130,8 +129,8 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat .append(" join m_currency curr on curr.code = journalEntry.currency_code ") .append(" left join m_external_asset_owner_journal_entry_mapping eajem on eajem.journal_entry_id = journalEntry.id ") .append(" left join m_external_asset_owner eao on eao.id = eajem.owner_id ") - .append(" left join m_savings_account sa on sa.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id NOT LIKE 'CA%' ") - .append(" left join m_current_account ca on ca.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id LIKE 'CA%' "); + .append(" left join m_savings_account sa on sa.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 ") + .append(" left join m_current_account ca on ca.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 "); if (associationParametersData.isTransactionDetailsRequired()) { sb.append(" left join m_loan_transaction as lt on journalEntry.loan_transaction_id = lt.id ") .append(" left join m_savings_account_transaction as st on journalEntry.savings_transaction_id = st.id ") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsConfig.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsConfig.java index 69fecbdb4..d7a120589 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsConfig.java @@ -1,70 +1,73 @@ -/** - * 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.account.jobs.executestandinginstructions; - -import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; -import org.apache.fineract.infrastructure.jobs.service.JobName; -import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; -import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.launch.support.RunIdIncrementer; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.step.builder.StepBuilder; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.transaction.PlatformTransactionManager; - -@Configuration -public class ExecuteStandingInstructionsConfig { - - @Autowired - private JobRepository jobRepository; - @Autowired - private PlatformTransactionManager transactionManager; - @Autowired - private StandingInstructionReadPlatformService standingInstructionReadPlatformService; - @Autowired - private JdbcTemplate jdbcTemplate; - @Autowired - private DatabaseSpecificSQLGenerator sqlGenerator; - @Autowired - private AccountTransfersWritePlatformService accountTransfersWritePlatformService; - - @Bean - protected Step executeStandingInstructionsStep() { - return new StepBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository) - .tasklet(executeStandingInstructionsTasklet(), transactionManager).build(); - } - - @Bean - public Job executeStandingInstructionsJob() { - return new JobBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository).start(executeStandingInstructionsStep()) - .incrementer(new RunIdIncrementer()).build(); - } - - @Bean - public ExecuteStandingInstructionsTasklet executeStandingInstructionsTasklet() { - return new ExecuteStandingInstructionsTasklet(standingInstructionReadPlatformService, jdbcTemplate, sqlGenerator, - accountTransfersWritePlatformService); - } -} +/** + * 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.account.jobs.executestandinginstructions; + +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository; +import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; +import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.launch.support.RunIdIncrementer; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; + +@Configuration +public class ExecuteStandingInstructionsConfig { + + @Autowired + private JobRepository jobRepository; + @Autowired + private PlatformTransactionManager transactionManager; + @Autowired + private StandingInstructionReadPlatformService standingInstructionReadPlatformService; + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private DatabaseSpecificSQLGenerator sqlGenerator; + @Autowired + private AccountTransfersWritePlatformService accountTransfersWritePlatformService; + @Autowired + private AccountTransferDetailRepository accountTransferDetailRepository; + + @Bean + protected Step executeStandingInstructionsStep() { + return new StepBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository) + .tasklet(executeStandingInstructionsTasklet(), transactionManager).build(); + } + + @Bean + public Job executeStandingInstructionsJob() { + return new JobBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository).start(executeStandingInstructionsStep()) + .incrementer(new RunIdIncrementer()).build(); + } + + @Bean + public ExecuteStandingInstructionsTasklet executeStandingInstructionsTasklet() { + return new ExecuteStandingInstructionsTasklet(standingInstructionReadPlatformService, jdbcTemplate, sqlGenerator, + accountTransfersWritePlatformService, accountTransferDetailRepository); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java index 352c012e3..cb3f87297 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java @@ -34,6 +34,8 @@ import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.portfolio.account.data.AccountTransferDTO; import org.apache.fineract.portfolio.account.data.StandingInstructionData; import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData; +import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository; +import org.apache.fineract.portfolio.account.domain.AccountTransferDetails; import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType; import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; import org.apache.fineract.portfolio.account.domain.StandingInstructionType; @@ -58,6 +60,7 @@ public class ExecuteStandingInstructionsTasklet implements Tasklet { private final JdbcTemplate jdbcTemplate; private final DatabaseSpecificSQLGenerator sqlGenerator; private final AccountTransfersWritePlatformService accountTransfersWritePlatformService; + private final AccountTransferDetailRepository accountTransferDetailRepository; @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { @@ -105,10 +108,15 @@ public class ExecuteStandingInstructionsTasklet implements Tasklet { final SavingsAccount fromSavingsAccount = null; final boolean isRegularTransaction = true; final boolean isExceptionForBalanceCheck = false; + // Load existing AccountTransferDetails so transactions are linked to + // this standing instruction and appear in its transaction history + final AccountTransferDetails existingDetails = data.getAccountDetailId() != null + ? accountTransferDetailRepository.findById(data.getAccountDetailId()).orElse(null) + : null; AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount, data.fromAccountTypeEnum(), data.toAccountTypeEnum(), data.getFromAccount().getId(), data.getToAccount().getId(), data.getName() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null, - data.transferTypeEnum().getValue(), null, null, ExternalId.empty(), null, null, fromSavingsAccount, + data.transferTypeEnum().getValue(), existingDetails, null, ExternalId.empty(), null, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck); final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java index aaf24bd3d..1ae645c56 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java @@ -34,6 +34,9 @@ import java.util.Collection; import java.util.List; import java.util.Locale; import lombok.RequiredArgsConstructor; +import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings; +import org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity; +import org.apache.fineract.accounting.journalentry.service.AccountingProcessorHelper; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.config.FineractProperties; @@ -96,6 +99,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer private final FineractProperties fineractProperties; private final CurrentAccountRepository currentAccountRepository; private final CurrentAccountTransactionRepository currentAccountTransactionRepository; + private final AccountingProcessorHelper accountingProcessorHelper; @Transactional @Override @@ -467,23 +471,29 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer .assembleFrom(accountTransferDTO.getToAccountId(), backdatedTxnsAllowedTill); final CurrentAccountTransaction withdrawal = performCurrentWithdrawal(fromCurrentAccount, - accountTransferDTO.getTransactionAmount(), accountTransferDTO.getTransactionDate()); + accountTransferDTO.getTransactionAmount(), accountTransferDTO.getTransactionDate(), + toSavingsAccount.productId(), toSavingsAccount.getId()); + // skipAccountingPostings=true: current account withdrawal leg (C-txn) already posts + // DR CURRENT ACCOUNT / CR SAVING INDIVIDUAL directly — each with its own account number. + // Savings deposit GL entries would produce a self-cancelling DR+CR on SAVING INDIVIDUAL — suppress them. final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(), - isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill); + isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill, true); - final AccountTransferDetails newDetails = AccountTransferDetails.currentToSavingsTransfer( - fromCurrentAccount.getOffice(), fromCurrentAccount.getClient(), fromCurrentAccount, - toSavingsAccount.office(), toSavingsAccount.getClient(), toSavingsAccount, - accountTransferDTO.getTransferType()); + if (accountTransferDetails == null) { + accountTransferDetails = AccountTransferDetails.currentToSavingsTransfer( + fromCurrentAccount.getOffice(), fromCurrentAccount.getClient(), fromCurrentAccount, + toSavingsAccount.office(), toSavingsAccount.getClient(), toSavingsAccount, + accountTransferDTO.getTransferType()); + } final Money amount = Money.of(toSavingsAccount.getCurrency(), accountTransferDTO.getTransactionAmount()); - newDetails.addAccountTransferTransaction(AccountTransferTransaction.savingsToSavingsTransfer( - newDetails, null, deposit, accountTransferDTO.getTransactionDate(), amount, + accountTransferDetails.addAccountTransferTransaction(AccountTransferTransaction.savingsToSavingsTransfer( + accountTransferDetails, null, deposit, accountTransferDTO.getTransactionDate(), amount, accountTransferDTO.getDescription())); - this.accountTransferDetailRepository.saveAndFlush(newDetails); - transferTransactionId = newDetails.getId(); + this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails); + transferTransactionId = accountTransferDetails.getId(); } else if (isSavingsToCurrentAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) { // --- Savings Account → Current Account --- @@ -494,27 +504,33 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer .orElseThrow(() -> new GeneralPlatformDomainRuleException( "error.msg.currentaccount.not.found", "Current account not found: " + accountTransferDTO.getToAccountId())); + // skipAccountingPostings=true: current account deposit leg (C-txn) posts + // DR SAVING INDIVIDUAL / CR CURRENT ACCOUNT directly. Savings withdrawal GL entries + // would produce a self-cancelling DR+CR on the same SAVING INDIVIDUAL GL — suppress them. final SavingsTransactionBooleanValues txnBooleans = new SavingsTransactionBooleanValues(isAccountTransfer, isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), false, - accountTransferDTO.isExceptionForBalanceCheck()); + accountTransferDTO.isExceptionForBalanceCheck(), true); final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(), txnBooleans, backdatedTxnsAllowedTill); performCurrentDeposit(toCurrentAccount, accountTransferDTO.getTransactionAmount(), - accountTransferDTO.getTransactionDate()); + accountTransferDTO.getTransactionDate(), + fromSavingsAccount.productId(), fromSavingsAccount.getId()); - final AccountTransferDetails newDetails = AccountTransferDetails.savingsToCurrentTransfer( - fromSavingsAccount.office(), fromSavingsAccount.getClient(), fromSavingsAccount, - toCurrentAccount.getOffice(), toCurrentAccount.getClient(), toCurrentAccount, - accountTransferDTO.getTransferType()); + if (accountTransferDetails == null) { + accountTransferDetails = AccountTransferDetails.savingsToCurrentTransfer( + fromSavingsAccount.office(), fromSavingsAccount.getClient(), fromSavingsAccount, + toCurrentAccount.getOffice(), toCurrentAccount.getClient(), toCurrentAccount, + accountTransferDTO.getTransferType()); + } final Money amount = Money.of(fromSavingsAccount.getCurrency(), accountTransferDTO.getTransactionAmount()); - newDetails.addAccountTransferTransaction(AccountTransferTransaction.savingsToSavingsTransfer( - newDetails, withdrawal, null, accountTransferDTO.getTransactionDate(), amount, + accountTransferDetails.addAccountTransferTransaction(AccountTransferTransaction.savingsToSavingsTransfer( + accountTransferDetails, withdrawal, null, accountTransferDTO.getTransactionDate(), amount, accountTransferDTO.getDescription())); - this.accountTransferDetailRepository.saveAndFlush(newDetails); - transferTransactionId = newDetails.getId(); + this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails); + transferTransactionId = accountTransferDetails.getId(); } else if (isCurrentToCurrentAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) { // --- Current Account → Current Account --- @@ -528,22 +544,26 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer "error.msg.currentaccount.not.found", "Current account not found: " + accountTransferDTO.getToAccountId())); performCurrentWithdrawal(fromCurrentAccount, accountTransferDTO.getTransactionAmount(), - accountTransferDTO.getTransactionDate()); + accountTransferDTO.getTransactionDate(), + toCurrentAccount.getProductId(), toCurrentAccount.getId()); performCurrentDeposit(toCurrentAccount, accountTransferDTO.getTransactionAmount(), - accountTransferDTO.getTransactionDate()); + accountTransferDTO.getTransactionDate(), + fromCurrentAccount.getProductId(), fromCurrentAccount.getId()); - final AccountTransferDetails newDetails = AccountTransferDetails.currentToCurrentTransfer( - fromCurrentAccount.getOffice(), fromCurrentAccount.getClient(), fromCurrentAccount, - toCurrentAccount.getOffice(), toCurrentAccount.getClient(), toCurrentAccount, - accountTransferDTO.getTransferType()); + if (accountTransferDetails == null) { + accountTransferDetails = AccountTransferDetails.currentToCurrentTransfer( + fromCurrentAccount.getOffice(), fromCurrentAccount.getClient(), fromCurrentAccount, + toCurrentAccount.getOffice(), toCurrentAccount.getClient(), toCurrentAccount, + accountTransferDTO.getTransferType()); + } final MonetaryCurrency currency = new MonetaryCurrency(fromCurrentAccount.getCurrencyCode(), fromCurrentAccount.getCurrencyDigits(), fromCurrentAccount.getCurrencyMultiplesOf()); final Money amount = Money.of(currency, accountTransferDTO.getTransactionAmount()); - newDetails.addAccountTransferTransaction(AccountTransferTransaction.savingsToSavingsTransfer( - newDetails, null, null, accountTransferDTO.getTransactionDate(), amount, + accountTransferDetails.addAccountTransferTransaction(AccountTransferTransaction.savingsToSavingsTransfer( + accountTransferDetails, null, null, accountTransferDTO.getTransactionDate(), amount, accountTransferDTO.getDescription())); - this.accountTransferDetailRepository.saveAndFlush(newDetails); - transferTransactionId = newDetails.getId(); + this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails); + transferTransactionId = accountTransferDetails.getId(); } else if (isCurrentToLoanAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) { // --- Current Account → Loan Account --- @@ -553,7 +573,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer "error.msg.currentaccount.not.found", "Current account not found: " + accountTransferDTO.getFromAccountId())); final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId()); - performCurrentWithdrawal(fromCurrentAccount, accountTransferDTO.getTransactionAmount(), + performCurrentWithdrawalForLoan(fromCurrentAccount, accountTransferDTO.getTransactionAmount(), accountTransferDTO.getTransactionDate()); ExternalId externalId = externalIdFactory.create(accountTransferDTO.getTxnExternalId().getValue()); @@ -656,10 +676,10 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer } /** - * Withdraw amount from a current account and save the transaction. - * Throws {@link GeneralPlatformDomainRuleException} if balance is insufficient. + * Withdraw for Current → Loan transfers. Loan domain service handles the loan-side GL, + * so current account side uses LIABILITY_TRANSFER as the counterpart (classic approach). */ - private CurrentAccountTransaction performCurrentWithdrawal(final CurrentAccount account, final BigDecimal amount, + private CurrentAccountTransaction performCurrentWithdrawalForLoan(final CurrentAccount account, final BigDecimal amount, final LocalDate txnDate) { final BigDecimal balance = account.getAccountBalanceDerived() != null ? account.getAccountBalanceDerived() : BigDecimal.ZERO; final BigDecimal onHold = account.getOnHoldFundsDerived() != null ? account.getOnHoldFundsDerived() : BigDecimal.ZERO; @@ -677,6 +697,40 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer account.setTotalWithdrawalsDerived( (account.getTotalWithdrawalsDerived() != null ? account.getTotalWithdrawalsDerived() : BigDecimal.ZERO).add(amount)); currentAccountRepository.save(account); + if (account.getProductId() != null && txn.getId() != null) { + final String currentTxnId = "CA" + txn.getId(); + accountingProcessorHelper.createCashBasedJournalEntriesAndReversalsForSavings(account.getOffice(), + account.getCurrencyCode(), CashAccountsForSavings.SAVINGS_CONTROL.getValue(), + FinancialActivity.LIABILITY_TRANSFER.getValue(), account.getProductId(), null, + account.getId(), currentTxnId, txnDate, amount, false); + } + return txn; + } + + /** + * Withdraw amount from a current account and save the transaction. + * Throws {@link GeneralPlatformDomainRuleException} if balance is insufficient. + */ + private CurrentAccountTransaction performCurrentWithdrawal(final CurrentAccount account, final BigDecimal amount, + final LocalDate txnDate, final Long counterpartProductId, final Long counterpartEntityId) { + final BigDecimal balance = account.getAccountBalanceDerived() != null ? account.getAccountBalanceDerived() : BigDecimal.ZERO; + final BigDecimal onHold = account.getOnHoldFundsDerived() != null ? account.getOnHoldFundsDerived() : BigDecimal.ZERO; + final BigDecimal available = balance.subtract(onHold); + if (!account.isAllowOverdraft() && amount.compareTo(available) > 0) { + throw new GeneralPlatformDomainRuleException("error.msg.current.account.insufficient.balance", + "Insufficient balance in current account " + account.getId() + + ". Available: " + available + ", Requested: " + amount, account.getId()); + } + final CurrentAccountTransaction txn = CurrentAccountTransaction.withdrawal(account, account.getOffice(), amount, txnDate, null); + final BigDecimal newBalance = balance.subtract(amount); + txn.setRunningBalanceDerived(newBalance); + currentAccountTransactionRepository.saveAndFlush(txn); + account.setAccountBalanceDerived(newBalance); + account.setTotalWithdrawalsDerived( + (account.getTotalWithdrawalsDerived() != null ? account.getTotalWithdrawalsDerived() : BigDecimal.ZERO).add(amount)); + currentAccountRepository.save(account); + // Post journal entries: DR current account GL, CR counterpart GL — each with correct entity/account number + postCurrentAccountTransferJournalEntries(account, txn, amount, txnDate, true, counterpartProductId, counterpartEntityId); return txn; } @@ -684,7 +738,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer * Deposit amount into a current account and save the transaction. */ private CurrentAccountTransaction performCurrentDeposit(final CurrentAccount account, final BigDecimal amount, - final LocalDate txnDate) { + final LocalDate txnDate, final Long counterpartProductId, final Long counterpartEntityId) { final BigDecimal balance = account.getAccountBalanceDerived() != null ? account.getAccountBalanceDerived() : BigDecimal.ZERO; final CurrentAccountTransaction txn = CurrentAccountTransaction.deposit(account, account.getOffice(), amount, txnDate, null); final BigDecimal newBalance = balance.add(amount); @@ -694,9 +748,67 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer account.setTotalDepositsDerived( (account.getTotalDepositsDerived() != null ? account.getTotalDepositsDerived() : BigDecimal.ZERO).add(amount)); currentAccountRepository.save(account); + // Post journal entries: DR counterpart GL, CR current account GL — each with correct entity/account number + postCurrentAccountTransferJournalEntries(account, txn, amount, txnDate, false, counterpartProductId, counterpartEntityId); return txn; } + /** + * Posts double-entry journal entries for a current account leg of an account transfer. + *

+ * Current Product GL mappings are stored under {@code PortfolioProductType.SAVING}, so the + * savings JE helper resolves them correctly. + *

+ * Transaction id is prefixed with "C" (e.g. {@code C57}) so the entries are NOT grouped with + * savings entries that share the same numeric id (those use prefix "S"). The helper only + * appends its own "S" prefix when the id is numeric; passing "C57" keeps it as-is. + * + * @param isWithdrawal {@code true} for the from-leg (DR control / CR liability transfer); + * {@code false} for the to-leg (DR liability transfer / CR control) + */ + /** + * Posts direct transfer journal entries between a current account and a counterpart savings/current account. + * Each entry carries the correct entity ID so the correct account number appears on each GL line. + * + * For withdrawal (current → savings): + * DR: current product GL (entity = current account) + * CR: counterpart product GL (entity = savings/current account) + * + * For deposit (savings → current): + * DR: counterpart product GL (entity = savings/current account) + * CR: current product GL (entity = current account) + * + * @param counterpartProductId savings/current product ID of the other account + * @param counterpartEntityId savings/current account ID of the other account + */ + private void postCurrentAccountTransferJournalEntries(final CurrentAccount account, final CurrentAccountTransaction txn, + final BigDecimal amount, final LocalDate txnDate, final boolean isWithdrawal, + final Long counterpartProductId, final Long counterpartEntityId) { + if (account.getProductId() == null || txn.getId() == null) { + return; + } + // Prefix "CA" so: + // 1. The helper does NOT re-prefix with "S" (avoids txn id collision with savings "S" prefix) + // 2. JournalEntryReadPlatformServiceImpl joins m_current_account (transaction_id LIKE 'CA%') + // so the correct current account number appears on the DR/CR entry. + final String currentTxnId = "CA" + txn.getId(); + if (isWithdrawal) { + // DR: current account GL CR: counterpart (savings/current) GL + accountingProcessorHelper.createDirectTransferJournalEntriesForSavings( + account.getOffice(), account.getCurrencyCode(), + account.getProductId(), account.getId(), + counterpartProductId, counterpartEntityId, + currentTxnId, txnDate, amount); + } else { + // DR: counterpart (savings/current) GL CR: current account GL + accountingProcessorHelper.createDirectTransferJournalEntriesForSavings( + account.getOffice(), account.getCurrencyCode(), + counterpartProductId, counterpartEntityId, + account.getProductId(), account.getId(), + currentTxnId, txnDate, amount); + } + } + @Override @Transactional public CommandProcessingResult refundByTransfer(JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java index c2bfdc826..58be11f63 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.account.starter; +import org.apache.fineract.accounting.journalentry.service.AccountingProcessorHelper; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; @@ -94,12 +95,13 @@ public class AccountConfiguration { AccountTransferDetailRepository accountTransferDetailRepository, LoanReadPlatformService loanReadPlatformService, GSIMRepositoy gsimRepository, ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory, FineractProperties fineractProperties, CurrentAccountRepository currentAccountRepository, - CurrentAccountTransactionRepository currentAccountTransactionRepository) { + CurrentAccountTransactionRepository currentAccountTransactionRepository, + AccountingProcessorHelper accountingProcessorHelper) { return new AccountTransfersWritePlatformServiceImpl(accountTransfersDataValidator, accountTransferAssembler, accountTransferRepository, savingsAccountAssembler, savingsAccountDomainService, loanAccountAssembler, loanAccountDomainService, savingsAccountWritePlatformService, accountTransferDetailRepository, loanReadPlatformService, gsimRepository, configurationDomainService, externalIdFactory, fineractProperties, - currentAccountRepository, currentAccountTransactionRepository); + currentAccountRepository, currentAccountTransactionRepository, accountingProcessorHelper); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java index fb16621dc..80b9c1a0d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java @@ -1,333 +1,356 @@ -/** - * 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.math.BigDecimal; -import java.math.MathContext; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; -import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsDepositBusinessEvent; -import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsWithdrawalBusinessEvent; -import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; -import org.apache.fineract.organisation.monetary.domain.Money; -import org.apache.fineract.organisation.monetary.domain.MoneyHelper; -import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; -import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType; -import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues; -import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO; -import org.apache.fineract.portfolio.savings.exception.DepositAccountTransactionNotAllowedException; -import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainService { - - private final PlatformSecurityContext context; - private final SavingsAccountRepositoryWrapper savingsAccountRepository; - private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; - private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; - private final JournalEntryWritePlatformService journalEntryWritePlatformService; - private final ConfigurationDomainService configurationDomainService; - private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository; - private final BusinessEventNotifierService businessEventNotifierService; - - @Autowired - public SavingsAccountDomainServiceJpa(final SavingsAccountRepositoryWrapper savingsAccountRepository, - final SavingsAccountTransactionRepository savingsAccountTransactionRepository, - final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, - final JournalEntryWritePlatformService journalEntryWritePlatformService, - final ConfigurationDomainService configurationDomainService, final PlatformSecurityContext context, - final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository, - final BusinessEventNotifierService businessEventNotifierService) { - this.savingsAccountRepository = savingsAccountRepository; - this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; - this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; - this.journalEntryWritePlatformService = journalEntryWritePlatformService; - this.configurationDomainService = configurationDomainService; - this.context = context; - this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository; - this.businessEventNotifierService = businessEventNotifierService; - } - - @Transactional - @Override - public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt, - final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, - final SavingsTransactionBooleanValues transactionBooleanValues, final boolean backdatedTxnsAllowedTill) { - context.authenticatedUser(); - account.validateForAccountBlock(); - account.validateForDebitBlock(); - final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService - .isSavingsInterestPostingAtCurrentPeriodEnd(); - final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate(); - final boolean postReversals = this.configurationDomainService.isReversalTransactionAllowed(); - final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); - if (transactionBooleanValues.isRegularTransaction() && !account.allowWithdrawal()) { - throw new DepositAccountTransactionNotAllowedException(account.getId(), "withdraw", account.depositAccountType()); - } - final Set existingTransactionIds = new HashSet<>(); - final LocalDate postInterestOnDate = null; - final Set existingReversedTransactionIds = new HashSet<>(); - - if (backdatedTxnsAllowedTill) { - updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds); - } else { - updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - } - - Integer accountType = null; - final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, null, accountType); - UUID refNo = UUID.randomUUID(); - final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee(), - backdatedTxnsAllowedTill, relaxingDaysConfigForPivotDate, refNo.toString()); - final MathContext mc = MathContext.DECIMAL64; - - final LocalDate today = DateUtils.getBusinessLocalDate(); - - if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) { - account.postInterest(mc, today, transactionBooleanValues.isInterestTransfer(), isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); - } else { - account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(), - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, - postReversals); - } - - List depositAccountOnHoldTransactions = null; - if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository - .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); - } - - account.validateAccountBalanceDoesNotBecomeNegative(transactionAmount, transactionBooleanValues.isExceptionForBalanceCheck(), - depositAccountOnHoldTransactions, backdatedTxnsAllowedTill); - - saveTransactionToGenerateTransactionId(withdrawal); - if (backdatedTxnsAllowedTill) { - // Update transactions separately - saveUpdatedTransactionsOfSavingsAccount(account.getSavingsAccountTransactionsWithPivotConfig()); - } - this.savingsAccountRepository.save(account); - - postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, transactionBooleanValues.isAccountTransfer(), - backdatedTxnsAllowedTill); - - businessEventNotifierService.notifyPostBusinessEvent(new SavingsWithdrawalBusinessEvent(withdrawal)); - return withdrawal; - } - - @Transactional - @Override - public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt, - final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, - final boolean isAccountTransfer, final boolean isRegularTransaction, final boolean backdatedTxnsAllowedTill) { - final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DEPOSIT; - return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, - savingsAccountTransactionType, backdatedTxnsAllowedTill); - } - - private SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt, - final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, - final boolean isAccountTransfer, final boolean isRegularTransaction, - final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill) { - context.authenticatedUser(); - account.validateForAccountBlock(); - account.validateForCreditBlock(); - - // Global configurations - final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService - .isSavingsInterestPostingAtCurrentPeriodEnd(); - final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); - final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate(); - if (isRegularTransaction && !account.allowDeposit()) { - throw new DepositAccountTransactionNotAllowedException(account.getId(), "deposit", account.depositAccountType()); - } - boolean isInterestTransfer = false; - final Set existingTransactionIds = new HashSet<>(); - final Set existingReversedTransactionIds = new HashSet<>(); - - if (backdatedTxnsAllowedTill) { - updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds); - } else { - updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - } - - Integer accountType = null; - final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, - paymentDetail, null, accountType); - UUID refNo = UUID.randomUUID(); - final SavingsAccountTransaction deposit = account.deposit(transactionDTO, savingsAccountTransactionType, backdatedTxnsAllowedTill, - relaxingDaysConfigForPivotDate, refNo.toString()); - final LocalDate postInterestOnDate = null; - final MathContext mc = MathContext.DECIMAL64; - - final LocalDate today = DateUtils.getBusinessLocalDate(); - boolean postReversals = this.configurationDomainService.isReversalTransactionAllowed(); - if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) { - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, backdatedTxnsAllowedTill, postReversals); - } else { - account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); - } - - saveTransactionToGenerateTransactionId(deposit); - - if (backdatedTxnsAllowedTill) { - // Update transactions separately - saveUpdatedTransactionsOfSavingsAccount(account.getSavingsAccountTransactionsWithPivotConfig()); - } - - this.savingsAccountRepository.saveAndFlush(account); - - postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill); - businessEventNotifierService.notifyPostBusinessEvent(new SavingsDepositBusinessEvent(deposit)); - return deposit; - } - - @Transactional - @Override - public SavingsAccountTransaction handleHold(final SavingsAccount account, BigDecimal amount, LocalDate transactionDate, - Boolean lienAllowed) { - return SavingsAccountTransaction.holdAmount(account, account.office(), null, transactionDate, - Money.of(account.getCurrency(), amount), lienAllowed); - } - - @Override - public SavingsAccountTransaction handleDividendPayout(final SavingsAccount account, final LocalDate transactionDate, - final BigDecimal transactionAmount, final boolean backdatedTxnsAllowedTill) { - final DateTimeFormatter fmt = null; - final PaymentDetail paymentDetail = null; - final boolean isAccountTransfer = false; - final boolean isRegularTransaction = true; - final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DIVIDEND_PAYOUT; - return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, - savingsAccountTransactionType, backdatedTxnsAllowedTill); - } - - private void updateExistingTransactionsDetails(SavingsAccount account, Set existingTransactionIds, - Set existingReversedTransactionIds) { - existingTransactionIds.addAll(account.findExistingTransactionIds()); - existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds()); - } - - private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) { - this.savingsAccountTransactionRepository.saveAndFlush(transaction); - return transaction.getId(); - } - - private void saveUpdatedTransactionsOfSavingsAccount(final List savingsAccountTransactions) { - this.savingsAccountTransactionRepository.saveAll(savingsAccountTransactions); - } - - private void updateTransactionDetailsWithPivotConfig(final SavingsAccount account, Set existingTransactionIds, - Set existingReversedTransactionIds) { - existingTransactionIds.addAll(account.findCurrentTransactionIdsWithPivotDateConfig()); - existingReversedTransactionIds.addAll(account.findCurrentReversedTransactionIdsWithPivotDateConfig()); - } - - private void postJournalEntries(final SavingsAccount savingsAccount, final Set existingTransactionIds, - final Set existingReversedTransactionIds, boolean isAccountTransfer, final boolean backdatedTxnsAllowedTill) { - - final Map accountingBridgeData = savingsAccount.deriveAccountingBridgeData(savingsAccount.getCurrency().getCode(), - existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill); - this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); - } - - @Transactional - @Override - public void postJournalEntries(final SavingsAccount account, final Set existingTransactionIds, - final Set existingReversedTransactionIds, final boolean backdatedTxnsAllowedTill) { - - final boolean isAccountTransfer = false; - postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill); - } - - @Override - public SavingsAccountTransaction handleReversal(SavingsAccount account, List savingsAccountTransactions, - boolean backdatedTxnsAllowedTill) { - - final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService - .isSavingsInterestPostingAtCurrentPeriodEnd(); - final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); - final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate(); - final boolean postReversals = true; - final Set existingTransactionIds = new HashSet<>(); - final Set existingReversedTransactionIds = new HashSet<>(); - - if (backdatedTxnsAllowedTill) { - updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds); - } else { - updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - } - List newTransactions = new ArrayList<>(); - SavingsAccountTransaction reversal = null; - - Set chargePaidBySet = null; - for (SavingsAccountTransaction savingsAccountTransaction : savingsAccountTransactions) { - reversal = SavingsAccountTransaction.reversal(savingsAccountTransaction); - chargePaidBySet = savingsAccountTransaction.getSavingsAccountChargesPaid(); - reversal.getSavingsAccountChargesPaid().addAll(chargePaidBySet); - account.undoTransaction(savingsAccountTransaction); - if (postReversals) { - newTransactions.add(reversal); - } - } - - boolean isInterestTransfer = false; - LocalDate postInterestOnDate = null; - final LocalDate today = DateUtils.getBusinessLocalDate(); - final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode()); - for (SavingsAccountTransaction savingsAccountTransaction : savingsAccountTransactions) { - if (savingsAccountTransaction.isPostInterestCalculationRequired() - && account.isBeforeLastPostingPeriod(savingsAccountTransaction.getTransactionDate(), backdatedTxnsAllowedTill)) { - - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, backdatedTxnsAllowedTill, postReversals); - } else { - account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); - } - account.validatePivotDateTransaction(savingsAccountTransaction.getTransactionDate(), backdatedTxnsAllowedTill, - relaxingDaysConfigForPivotDate, "savingsaccount"); - account.validateAccountBalanceDoesNotBecomeNegativeMinimal(savingsAccountTransaction.getAmount(), false); - account.activateAccountBasedOnBalance(); - } - this.savingsAccountRepository.save(account); - newTransactions.addAll(account.getSavingsAccountTransactionsWithPivotConfig()); - this.savingsAccountTransactionRepository.saveAll(newTransactions); - postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false, backdatedTxnsAllowedTill); - - return reversal; - } -} +/** + * 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.math.BigDecimal; +import java.math.MathContext; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsDepositBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsWithdrawalBusinessEvent; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; +import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; +import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType; +import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO; +import org.apache.fineract.portfolio.savings.exception.DepositAccountTransactionNotAllowedException; +import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainService { + + private final PlatformSecurityContext context; + private final SavingsAccountRepositoryWrapper savingsAccountRepository; + private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; + private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; + private final JournalEntryWritePlatformService journalEntryWritePlatformService; + private final ConfigurationDomainService configurationDomainService; + private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository; + private final BusinessEventNotifierService businessEventNotifierService; + + @Autowired + public SavingsAccountDomainServiceJpa(final SavingsAccountRepositoryWrapper savingsAccountRepository, + final SavingsAccountTransactionRepository savingsAccountTransactionRepository, + final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, + final JournalEntryWritePlatformService journalEntryWritePlatformService, + final ConfigurationDomainService configurationDomainService, final PlatformSecurityContext context, + final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository, + final BusinessEventNotifierService businessEventNotifierService) { + this.savingsAccountRepository = savingsAccountRepository; + this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; + this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; + this.journalEntryWritePlatformService = journalEntryWritePlatformService; + this.configurationDomainService = configurationDomainService; + this.context = context; + this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository; + this.businessEventNotifierService = businessEventNotifierService; + } + + @Transactional + @Override + public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt, + final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, + final SavingsTransactionBooleanValues transactionBooleanValues, final boolean backdatedTxnsAllowedTill) { + context.authenticatedUser(); + account.validateForAccountBlock(); + account.validateForDebitBlock(); + final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService + .isSavingsInterestPostingAtCurrentPeriodEnd(); + final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate(); + final boolean postReversals = this.configurationDomainService.isReversalTransactionAllowed(); + final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); + if (transactionBooleanValues.isRegularTransaction() && !account.allowWithdrawal()) { + throw new DepositAccountTransactionNotAllowedException(account.getId(), "withdraw", account.depositAccountType()); + } + final Set existingTransactionIds = new HashSet<>(); + final LocalDate postInterestOnDate = null; + final Set existingReversedTransactionIds = new HashSet<>(); + + if (backdatedTxnsAllowedTill) { + updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds); + } else { + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); + } + + Integer accountType = null; + final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, + paymentDetail, null, accountType); + UUID refNo = UUID.randomUUID(); + final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee(), + backdatedTxnsAllowedTill, relaxingDaysConfigForPivotDate, refNo.toString()); + final MathContext mc = MathContext.DECIMAL64; + + final LocalDate today = DateUtils.getBusinessLocalDate(); + + if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) { + account.postInterest(mc, today, transactionBooleanValues.isInterestTransfer(), isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); + } else { + account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(), + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, + postReversals); + } + + List depositAccountOnHoldTransactions = null; + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + } + + account.validateAccountBalanceDoesNotBecomeNegative(transactionAmount, transactionBooleanValues.isExceptionForBalanceCheck(), + depositAccountOnHoldTransactions, backdatedTxnsAllowedTill); + + saveTransactionToGenerateTransactionId(withdrawal); + if (backdatedTxnsAllowedTill) { + // Update transactions separately + saveUpdatedTransactionsOfSavingsAccount(account.getSavingsAccountTransactionsWithPivotConfig()); + } + this.savingsAccountRepository.save(account); + + if (!transactionBooleanValues.isSkipAccountingPostings()) { + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, + transactionBooleanValues.isAccountTransfer(), backdatedTxnsAllowedTill); + } + + businessEventNotifierService.notifyPostBusinessEvent(new SavingsWithdrawalBusinessEvent(withdrawal)); + return withdrawal; + } + + @Transactional + @Override + public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt, + final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, + final boolean isAccountTransfer, final boolean isRegularTransaction, final boolean backdatedTxnsAllowedTill) { + final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DEPOSIT; + return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, + savingsAccountTransactionType, backdatedTxnsAllowedTill, false); + } + + @Override + public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt, + final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, + final boolean isAccountTransfer, final boolean isRegularTransaction, final boolean backdatedTxnsAllowedTill, + final boolean skipAccountingPostings) { + final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DEPOSIT; + return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, + savingsAccountTransactionType, backdatedTxnsAllowedTill, skipAccountingPostings); + } + + private SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt, + final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, + final boolean isAccountTransfer, final boolean isRegularTransaction, + final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill) { + return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, + savingsAccountTransactionType, backdatedTxnsAllowedTill, false); + } + + private SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt, + final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, + final boolean isAccountTransfer, final boolean isRegularTransaction, + final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill, + final boolean skipAccountingPostings) { + context.authenticatedUser(); + account.validateForAccountBlock(); + account.validateForCreditBlock(); + + // Global configurations + final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService + .isSavingsInterestPostingAtCurrentPeriodEnd(); + final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); + final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate(); + if (isRegularTransaction && !account.allowDeposit()) { + throw new DepositAccountTransactionNotAllowedException(account.getId(), "deposit", account.depositAccountType()); + } + boolean isInterestTransfer = false; + final Set existingTransactionIds = new HashSet<>(); + final Set existingReversedTransactionIds = new HashSet<>(); + + if (backdatedTxnsAllowedTill) { + updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds); + } else { + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); + } + + Integer accountType = null; + final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, + paymentDetail, null, accountType); + UUID refNo = UUID.randomUUID(); + final SavingsAccountTransaction deposit = account.deposit(transactionDTO, savingsAccountTransactionType, backdatedTxnsAllowedTill, + relaxingDaysConfigForPivotDate, refNo.toString()); + final LocalDate postInterestOnDate = null; + final MathContext mc = MathContext.DECIMAL64; + + final LocalDate today = DateUtils.getBusinessLocalDate(); + boolean postReversals = this.configurationDomainService.isReversalTransactionAllowed(); + if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) { + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + postInterestOnDate, backdatedTxnsAllowedTill, postReversals); + } else { + account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); + } + + saveTransactionToGenerateTransactionId(deposit); + + if (backdatedTxnsAllowedTill) { + // Update transactions separately + saveUpdatedTransactionsOfSavingsAccount(account.getSavingsAccountTransactionsWithPivotConfig()); + } + + this.savingsAccountRepository.saveAndFlush(account); + + if (!skipAccountingPostings) { + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill); + } + businessEventNotifierService.notifyPostBusinessEvent(new SavingsDepositBusinessEvent(deposit)); + return deposit; + } + + @Transactional + @Override + public SavingsAccountTransaction handleHold(final SavingsAccount account, BigDecimal amount, LocalDate transactionDate, + Boolean lienAllowed) { + return SavingsAccountTransaction.holdAmount(account, account.office(), null, transactionDate, + Money.of(account.getCurrency(), amount), lienAllowed); + } + + @Override + public SavingsAccountTransaction handleDividendPayout(final SavingsAccount account, final LocalDate transactionDate, + final BigDecimal transactionAmount, final boolean backdatedTxnsAllowedTill) { + final DateTimeFormatter fmt = null; + final PaymentDetail paymentDetail = null; + final boolean isAccountTransfer = false; + final boolean isRegularTransaction = true; + final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DIVIDEND_PAYOUT; + return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, + savingsAccountTransactionType, backdatedTxnsAllowedTill); + } + + private void updateExistingTransactionsDetails(SavingsAccount account, Set existingTransactionIds, + Set existingReversedTransactionIds) { + existingTransactionIds.addAll(account.findExistingTransactionIds()); + existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds()); + } + + private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) { + this.savingsAccountTransactionRepository.saveAndFlush(transaction); + return transaction.getId(); + } + + private void saveUpdatedTransactionsOfSavingsAccount(final List savingsAccountTransactions) { + this.savingsAccountTransactionRepository.saveAll(savingsAccountTransactions); + } + + private void updateTransactionDetailsWithPivotConfig(final SavingsAccount account, Set existingTransactionIds, + Set existingReversedTransactionIds) { + existingTransactionIds.addAll(account.findCurrentTransactionIdsWithPivotDateConfig()); + existingReversedTransactionIds.addAll(account.findCurrentReversedTransactionIdsWithPivotDateConfig()); + } + + private void postJournalEntries(final SavingsAccount savingsAccount, final Set existingTransactionIds, + final Set existingReversedTransactionIds, boolean isAccountTransfer, final boolean backdatedTxnsAllowedTill) { + + final Map accountingBridgeData = savingsAccount.deriveAccountingBridgeData(savingsAccount.getCurrency().getCode(), + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill); + this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); + } + + @Transactional + @Override + public void postJournalEntries(final SavingsAccount account, final Set existingTransactionIds, + final Set existingReversedTransactionIds, final boolean backdatedTxnsAllowedTill) { + + final boolean isAccountTransfer = false; + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill); + } + + @Override + public SavingsAccountTransaction handleReversal(SavingsAccount account, List savingsAccountTransactions, + boolean backdatedTxnsAllowedTill) { + + final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService + .isSavingsInterestPostingAtCurrentPeriodEnd(); + final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); + final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate(); + final boolean postReversals = true; + final Set existingTransactionIds = new HashSet<>(); + final Set existingReversedTransactionIds = new HashSet<>(); + + if (backdatedTxnsAllowedTill) { + updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds); + } else { + updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); + } + List newTransactions = new ArrayList<>(); + SavingsAccountTransaction reversal = null; + + Set chargePaidBySet = null; + for (SavingsAccountTransaction savingsAccountTransaction : savingsAccountTransactions) { + reversal = SavingsAccountTransaction.reversal(savingsAccountTransaction); + chargePaidBySet = savingsAccountTransaction.getSavingsAccountChargesPaid(); + reversal.getSavingsAccountChargesPaid().addAll(chargePaidBySet); + account.undoTransaction(savingsAccountTransaction); + if (postReversals) { + newTransactions.add(reversal); + } + } + + boolean isInterestTransfer = false; + LocalDate postInterestOnDate = null; + final LocalDate today = DateUtils.getBusinessLocalDate(); + final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode()); + for (SavingsAccountTransaction savingsAccountTransaction : savingsAccountTransactions) { + if (savingsAccountTransaction.isPostInterestCalculationRequired() + && account.isBeforeLastPostingPeriod(savingsAccountTransaction.getTransactionDate(), backdatedTxnsAllowedTill)) { + + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + postInterestOnDate, backdatedTxnsAllowedTill, postReversals); + } else { + account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); + } + account.validatePivotDateTransaction(savingsAccountTransaction.getTransactionDate(), backdatedTxnsAllowedTill, + relaxingDaysConfigForPivotDate, "savingsaccount"); + account.validateAccountBalanceDoesNotBecomeNegativeMinimal(savingsAccountTransaction.getAmount(), false); + account.activateAccountBasedOnBalance(); + } + this.savingsAccountRepository.save(account); + newTransactions.addAll(account.getSavingsAccountTransactionsWithPivotConfig()); + this.savingsAccountTransactionRepository.saveAll(newTransactions); + postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false, backdatedTxnsAllowedTill); + + return reversal; + } +} diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/SavingsTransactionBooleanValues.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/SavingsTransactionBooleanValues.java index 2cef4adfc..f3f8a79cb 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/SavingsTransactionBooleanValues.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/SavingsTransactionBooleanValues.java @@ -1,59 +1,70 @@ -/** - * 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; - -public class SavingsTransactionBooleanValues { - - private final boolean isAccountTransfer; - private final boolean isRegularTransaction; - private final boolean isApplyWithdrawFee; - private final boolean isInterestTransfer; - private final boolean isExceptionForBalanceCheck; - - public SavingsTransactionBooleanValues(final boolean isAccountTransfer, final boolean isRegularTransaction, - final boolean isApplyWithdrawFee, final boolean isInterestTransfer, final boolean isExceptionForBalanceCheck) { - - this.isAccountTransfer = isAccountTransfer; - this.isRegularTransaction = isRegularTransaction; - this.isApplyWithdrawFee = isApplyWithdrawFee; - this.isInterestTransfer = isInterestTransfer; - this.isExceptionForBalanceCheck = isExceptionForBalanceCheck; - } - - public boolean isAccountTransfer() { - return this.isAccountTransfer; - } - - public boolean isRegularTransaction() { - return this.isRegularTransaction; - } - - public boolean isApplyWithdrawFee() { - return this.isApplyWithdrawFee; - } - - public boolean isInterestTransfer() { - return this.isInterestTransfer; - } - - public boolean isExceptionForBalanceCheck() { - return this.isExceptionForBalanceCheck; - } - -} +/** + * 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; + +public class SavingsTransactionBooleanValues { + + private final boolean isAccountTransfer; + private final boolean isRegularTransaction; + private final boolean isApplyWithdrawFee; + private final boolean isInterestTransfer; + private final boolean isExceptionForBalanceCheck; + private final boolean skipAccountingPostings; + + public SavingsTransactionBooleanValues(final boolean isAccountTransfer, final boolean isRegularTransaction, + final boolean isApplyWithdrawFee, final boolean isInterestTransfer, final boolean isExceptionForBalanceCheck) { + this(isAccountTransfer, isRegularTransaction, isApplyWithdrawFee, isInterestTransfer, isExceptionForBalanceCheck, false); + } + + public SavingsTransactionBooleanValues(final boolean isAccountTransfer, final boolean isRegularTransaction, + final boolean isApplyWithdrawFee, final boolean isInterestTransfer, final boolean isExceptionForBalanceCheck, + final boolean skipAccountingPostings) { + this.isAccountTransfer = isAccountTransfer; + this.isRegularTransaction = isRegularTransaction; + this.isApplyWithdrawFee = isApplyWithdrawFee; + this.isInterestTransfer = isInterestTransfer; + this.isExceptionForBalanceCheck = isExceptionForBalanceCheck; + this.skipAccountingPostings = skipAccountingPostings; + } + + public boolean isAccountTransfer() { + return this.isAccountTransfer; + } + + public boolean isRegularTransaction() { + return this.isRegularTransaction; + } + + public boolean isApplyWithdrawFee() { + return this.isApplyWithdrawFee; + } + + public boolean isInterestTransfer() { + return this.isInterestTransfer; + } + + public boolean isExceptionForBalanceCheck() { + return this.isExceptionForBalanceCheck; + } + + public boolean isSkipAccountingPostings() { + return this.skipAccountingPostings; + } + +} diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountDomainService.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountDomainService.java index 325c9dd8a..bdd17f5b2 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountDomainService.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountDomainService.java @@ -1,51 +1,55 @@ -/** - * 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.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.Set; -import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; -import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues; -import org.apache.fineract.portfolio.savings.domain.SavingsAccount; -import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; - -public interface SavingsAccountDomainService { - - SavingsAccountTransaction handleWithdrawal(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate, - BigDecimal transactionAmount, PaymentDetail paymentDetail, SavingsTransactionBooleanValues transactionBooleanValues, - boolean backdatedTxnsAllowedTill); - - SavingsAccountTransaction handleDeposit(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate, - BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isAccountTransfer, boolean isRegularTransaction, - boolean backdatedTxnsAllowedTill); - - void postJournalEntries(SavingsAccount savingsAccount, Set existingTransactionIds, Set existingReversedTransactionIds, - boolean backdatedTxnsAllowedTill); - - SavingsAccountTransaction handleDividendPayout(SavingsAccount account, LocalDate transactionDate, BigDecimal transactionAmount, - boolean backdatedTxnsAllowedTill); - - SavingsAccountTransaction handleReversal(SavingsAccount account, List savingsAccountTransactions, - boolean backdatedTxnsAllowedTill); - - SavingsAccountTransaction handleHold(SavingsAccount account, BigDecimal amount, LocalDate transactionDate, Boolean lienAllowed); -} +/** + * 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.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Set; +import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; +import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues; +import org.apache.fineract.portfolio.savings.domain.SavingsAccount; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; + +public interface SavingsAccountDomainService { + + SavingsAccountTransaction handleWithdrawal(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate, + BigDecimal transactionAmount, PaymentDetail paymentDetail, SavingsTransactionBooleanValues transactionBooleanValues, + boolean backdatedTxnsAllowedTill); + + SavingsAccountTransaction handleDeposit(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate, + BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isAccountTransfer, boolean isRegularTransaction, + boolean backdatedTxnsAllowedTill); + + SavingsAccountTransaction handleDeposit(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate, + BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isAccountTransfer, boolean isRegularTransaction, + boolean backdatedTxnsAllowedTill, boolean skipAccountingPostings); + + void postJournalEntries(SavingsAccount savingsAccount, Set existingTransactionIds, Set existingReversedTransactionIds, + boolean backdatedTxnsAllowedTill); + + SavingsAccountTransaction handleDividendPayout(SavingsAccount account, LocalDate transactionDate, BigDecimal transactionAmount, + boolean backdatedTxnsAllowedTill); + + SavingsAccountTransaction handleReversal(SavingsAccount account, List savingsAccountTransactions, + boolean backdatedTxnsAllowedTill); + + SavingsAccountTransaction handleHold(SavingsAccount account, BigDecimal amount, LocalDate transactionDate, Boolean lienAllowed); +}