fix(deposits): enforce savings-only maturity transfers and fix duplicate GL posting on FD/RD closure
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Adds validation so Transfer-to-Savings can only target a real savings account, with a proper user-facing message instead of the generic validation error. Fixes three duplicate journal-entry bugs uncovered while testing FD/RD maturity: the dormant-accounts job re-posting already-closed accounts (escheat/setSubStatusInactive/setSubStatusDormant), and the maturity-closure flow itself re-deriving accounting for a withdrawal that the account-transfer service had already posted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+13
@@ -130,6 +130,19 @@ public class DataValidatorBuilder {
|
|||||||
this.dataValidationErrors.add(error);
|
this.dataValidationErrors.add(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #failWithCode(String, Object...)}, but the caller's own message is what's actually shown to the
|
||||||
|
* user (that method always renders the generic "Failed data validation due to: {errorCode}." regardless of the
|
||||||
|
* args passed to it).
|
||||||
|
*/
|
||||||
|
public void failWithCodeAndMessage(final String errorCode, final String defaultUserMessage,
|
||||||
|
final Object... defaultUserMessageArgs) {
|
||||||
|
String validationErrorCode = "validation.msg." + this.resource + "." + this.parameter + "." + errorCode;
|
||||||
|
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode, defaultUserMessage, this.parameter,
|
||||||
|
this.value, defaultUserMessageArgs);
|
||||||
|
this.dataValidationErrors.add(error);
|
||||||
|
}
|
||||||
|
|
||||||
public void failWithCodeNoParameterAddedToErrorCode(final String errorCode, final Object... defaultUserMessageArgs) {
|
public void failWithCodeNoParameterAddedToErrorCode(final String errorCode, final Object... defaultUserMessageArgs) {
|
||||||
String validationErrorCode = "validation.msg." + this.resource + "." + errorCode;
|
String validationErrorCode = "validation.msg." + this.resource + "." + errorCode;
|
||||||
String defaultEnglishMessage = "Failed data validation due to: " + errorCode + ".";
|
String defaultEnglishMessage = "Failed data validation due to: " + errorCode + ".";
|
||||||
|
|||||||
+5
@@ -1263,6 +1263,11 @@ public class AccountingProcessorHelper {
|
|||||||
accountMapping = paymentChannelSpecificAccountMapping;
|
accountMapping = paymentChannelSpecificAccountMapping;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (accountMapping == null) {
|
||||||
|
throw new ProductToGLAccountMappingNotFoundException(PortfolioProductType.SAVING, savingsProductId,
|
||||||
|
CashAccountsForSavings.fromInt(accountMappingTypeId).toString());
|
||||||
|
}
|
||||||
glAccount = accountMapping.getGlAccount();
|
glAccount = accountMapping.getGlAccount();
|
||||||
}
|
}
|
||||||
return glAccount;
|
return glAccount;
|
||||||
|
|||||||
+47
-1
@@ -97,6 +97,7 @@ import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
|||||||
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
|
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -105,11 +106,14 @@ public class DepositAccountDataValidator {
|
|||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
private final DepositProductDataValidator productDataValidator;
|
private final DepositProductDataValidator productDataValidator;
|
||||||
|
private final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public DepositAccountDataValidator(final FromJsonHelper fromApiJsonHelper, final DepositProductDataValidator productDataValidator) {
|
public DepositAccountDataValidator(final FromJsonHelper fromApiJsonHelper, final DepositProductDataValidator productDataValidator,
|
||||||
|
final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper) {
|
||||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||||
this.productDataValidator = productDataValidator;
|
this.productDataValidator = productDataValidator;
|
||||||
|
this.savingsAccountRepositoryWrapper = savingsAccountRepositoryWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validateFixedDepositForSubmit(final String json) {
|
public void validateFixedDepositForSubmit(final String json) {
|
||||||
@@ -351,10 +355,31 @@ public class DepositAccountDataValidator {
|
|||||||
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).notNull()
|
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).notNull()
|
||||||
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
||||||
|
|
||||||
|
// Withdraw Deposit books the maturity proceeds straight out of the bank's cash/vault GL the
|
||||||
|
// moment the account matures -- before the customer has actually come in to collect it. For
|
||||||
|
// maturities processed automatically by the scheduled job (not a live counter transaction),
|
||||||
|
// that books cash as disbursed while it is still physically in the vault, so this option is
|
||||||
|
// disabled: every account must resolve to Transfer to Savings or a Reinvest option instead.
|
||||||
|
if (depositRolloverId.equals(DepositAccountOnClosureType.WITHDRAW_DEPOSIT.getValue())) {
|
||||||
|
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).failWithCodeAndMessage(
|
||||||
|
"maturity.instruction.withdraw.deposit.not.allowed",
|
||||||
|
"Withdraw Deposit is not a valid maturity instruction -- choose Transfer to Savings or a Reinvest option.");
|
||||||
|
}
|
||||||
|
|
||||||
if (depositRolloverId.equals(DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getValue())) {
|
if (depositRolloverId.equals(DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getValue())) {
|
||||||
final Long transferToSavingsId = this.fromApiJsonHelper.extractLongNamed(transferToSavingsIdParamName, element);
|
final Long transferToSavingsId = this.fromApiJsonHelper.extractLongNamed(transferToSavingsIdParamName, element);
|
||||||
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId).notNull()
|
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId).notNull()
|
||||||
.longGreaterThanZero();
|
.longGreaterThanZero();
|
||||||
|
|
||||||
|
// Transfer to Savings can only ever post into a genuine Savings account -- Current accounts
|
||||||
|
// live in a separate ledger/module that this maturity-closure transfer cannot write into.
|
||||||
|
if (transferToSavingsId != null && transferToSavingsId > 0
|
||||||
|
&& !this.savingsAccountRepositoryWrapper.existsWithDepositAccountType(transferToSavingsId,
|
||||||
|
DepositAccountType.SAVINGS_DEPOSIT)) {
|
||||||
|
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId)
|
||||||
|
.failWithCodeAndMessage("transfer.to.savings.id.is.not.a.savings.account",
|
||||||
|
"The selected account is not a Savings account -- Transfer to Savings can only target a Savings account.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,10 +518,31 @@ public class DepositAccountDataValidator {
|
|||||||
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).notNull()
|
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).notNull()
|
||||||
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
||||||
|
|
||||||
|
// Withdraw Deposit books the maturity proceeds straight out of the bank's cash/vault GL the
|
||||||
|
// moment the account matures -- before the customer has actually come in to collect it. For
|
||||||
|
// maturities processed automatically by the scheduled job (not a live counter transaction),
|
||||||
|
// that books cash as disbursed while it is still physically in the vault, so this option is
|
||||||
|
// disabled: every account must resolve to Transfer to Savings or a Reinvest option instead.
|
||||||
|
if (depositRolloverId.equals(DepositAccountOnClosureType.WITHDRAW_DEPOSIT.getValue())) {
|
||||||
|
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).failWithCodeAndMessage(
|
||||||
|
"maturity.instruction.withdraw.deposit.not.allowed",
|
||||||
|
"Withdraw Deposit is not a valid maturity instruction -- choose Transfer to Savings or a Reinvest option.");
|
||||||
|
}
|
||||||
|
|
||||||
if (depositRolloverId.equals(DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getValue())) {
|
if (depositRolloverId.equals(DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getValue())) {
|
||||||
final Long transferToSavingsId = this.fromApiJsonHelper.extractLongNamed(transferToSavingsIdParamName, element);
|
final Long transferToSavingsId = this.fromApiJsonHelper.extractLongNamed(transferToSavingsIdParamName, element);
|
||||||
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId).notNull()
|
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId).notNull()
|
||||||
.longGreaterThanZero();
|
.longGreaterThanZero();
|
||||||
|
|
||||||
|
// Transfer to Savings can only ever post into a genuine Savings account -- Current accounts
|
||||||
|
// live in a separate ledger/module that this maturity-closure transfer cannot write into.
|
||||||
|
if (transferToSavingsId != null && transferToSavingsId > 0
|
||||||
|
&& !this.savingsAccountRepositoryWrapper.existsWithDepositAccountType(transferToSavingsId,
|
||||||
|
DepositAccountType.SAVINGS_DEPOSIT)) {
|
||||||
|
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId)
|
||||||
|
.failWithCodeAndMessage("transfer.to.savings.id.is.not.a.savings.account",
|
||||||
|
"The selected account is not a Savings account -- Transfer to Savings can only target a Savings account.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-7
@@ -555,14 +555,19 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateAlreadyPostedTransactions(final Set<Long> existingTransactionIds, final SavingsAccount savingsAccount) {
|
private void updateAlreadyPostedTransactions(final Set<Long> existingTransactionIds, final SavingsAccount savingsAccount) {
|
||||||
List<SavingsAccountTransaction> transactions = savingsAccount.getTransactions();
|
// accountTransfersWritePlatformService.transferFunds() already posts accounting for the
|
||||||
int size = transactions.size();
|
// withdrawal it creates on this account (via SavingsAccountDomainServiceJpa#handleWithdrawal),
|
||||||
for (int i = size - 1;; i--) {
|
// so that withdrawal must be added to existingTransactionIds here or the caller's own
|
||||||
SavingsAccountTransaction transaction = transactions.get(i);
|
// postJournalEntries() re-derives and re-posts it a second time. The previous implementation
|
||||||
if (transaction.isWithdrawal() || transaction.isWithdrawalFee()) {
|
// found it by walking the transaction list backwards from the end while the type stayed
|
||||||
|
// withdrawal/withdrawal-fee -- but @OneToMany collections only guarantee @OrderBy ordering on
|
||||||
|
// a fresh fetch, not after an in-memory append, so a same-day interest posting or any other
|
||||||
|
// ordering quirk could put the new withdrawal anywhere in the list and break the walk
|
||||||
|
// silently. Scanning every transaction and checking against the original snapshot is correct
|
||||||
|
// regardless of collection order.
|
||||||
|
for (final SavingsAccountTransaction transaction : savingsAccount.getTransactions()) {
|
||||||
|
if ((transaction.isWithdrawal() || transaction.isWithdrawalFee()) && !existingTransactionIds.contains(transaction.getId())) {
|
||||||
existingTransactionIds.add(transaction.getId());
|
existingTransactionIds.add(transaction.getId());
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -554,7 +554,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
|||||||
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
|
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
|
||||||
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
|
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
|
||||||
withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions,
|
withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions,
|
||||||
periodFrequencyTypeOptions);
|
periodFrequencyTypeOptions, savingsAccountDatas, maturityInstructionOptions);
|
||||||
template = RecurringDepositAccountData.withInterestChartAndRecurringDetails((RecurringDepositAccountData) template,
|
template = RecurringDepositAccountData.withInterestChartAndRecurringDetails((RecurringDepositAccountData) template,
|
||||||
accountChart, null, null);
|
accountChart, null, null);
|
||||||
// Re-apply CBS product fields lost during factory method object creation
|
// Re-apply CBS product fields lost during factory method object creation
|
||||||
@@ -626,7 +626,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
|||||||
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
|
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
|
||||||
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
|
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
|
||||||
withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions,
|
withdrawalFeeTypeOptions, transactions, charges, chargeOptions, preClosurePenalInterestOnTypeOptions,
|
||||||
periodFrequencyTypeOptions);
|
periodFrequencyTypeOptions, savingsAccountDatas, maturityInstructionOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
@@ -1393,6 +1393,15 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
|
|||||||
this.depositAccountDomainService.handleFDAccountMaturityClosure(fdAccount, null, user, fmt, fdAccount.maturityDate(),
|
this.depositAccountDomainService.handleFDAccountMaturityClosure(fdAccount, null, user, fmt, fdAccount.maturityDate(),
|
||||||
fdAccount.getOnAccountClosureId(), toSavingsId, "Apply maturity instructions", changes);
|
fdAccount.getOnAccountClosureId(), toSavingsId, "Apply maturity instructions", changes);
|
||||||
|
|
||||||
|
// Every maturity-instruction branch takes the balance out of this account through
|
||||||
|
// SavingsAccountDomainServiceJpa#handleWithdrawal, which posts that withdrawal's journal
|
||||||
|
// entries itself. existingTransactionIds was snapshotted at the top of this method --
|
||||||
|
// before that withdrawal existed -- so the postJournalEntries() call at the end would
|
||||||
|
// treat it as new and post it a second time. Interest postings created by
|
||||||
|
// updateMaturityStatus() above are still genuinely unposted at this point and must stay
|
||||||
|
// out of the snapshot so that final call can post them exactly once.
|
||||||
|
markWithdrawalsAsAlreadyPosted(account, existingTransactionIds);
|
||||||
|
|
||||||
if (changes.get("reinvestedDepositId") != null) {
|
if (changes.get("reinvestedDepositId") != null) {
|
||||||
Long reinvestedDepositId = (Long) changes.get("reinvestedDepositId");
|
Long reinvestedDepositId = (Long) changes.get("reinvestedDepositId");
|
||||||
Money amountForDeposit = account.activateWithBalance();
|
Money amountForDeposit = account.activateWithBalance();
|
||||||
@@ -1421,6 +1430,19 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
|
|||||||
existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
|
existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the account's withdrawal (and withdrawal-fee) transactions as already accounted for, so a
|
||||||
|
* subsequent postJournalEntries() built on an older snapshot does not post them again. Used after a
|
||||||
|
* maturity closure, whose withdrawal leg posts its own journal entries.
|
||||||
|
*/
|
||||||
|
private void markWithdrawalsAsAlreadyPosted(final SavingsAccount account, final Set<Long> existingTransactionIds) {
|
||||||
|
for (final SavingsAccountTransaction transaction : account.getTransactions()) {
|
||||||
|
if (transaction.getId() != null && (transaction.isWithdrawal() || transaction.isWithdrawalFee())) {
|
||||||
|
existingTransactionIds.add(transaction.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
|
private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
|
||||||
final Set<Long> existingReversedTransactionIds) {
|
final Set<Long> existingReversedTransactionIds) {
|
||||||
|
|
||||||
|
|||||||
+24
@@ -1676,6 +1676,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
|||||||
@Override
|
@Override
|
||||||
public void setSubStatusInactive(Long savingsId) {
|
public void setSubStatusInactive(Long savingsId) {
|
||||||
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
|
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
|
||||||
|
// Same snapshot-vs-execution gap as escheat() below: the dormant-accounts batch selects
|
||||||
|
// pending-inactive ids from a query that requires status = ACTIVE, but the account can close
|
||||||
|
// through another path (e.g. deposit maturity) before this call runs. Re-deriving accounting
|
||||||
|
// off the full transaction list at that point re-posts entries that closure already posted.
|
||||||
|
if (!account.isActive()) {
|
||||||
|
log.warn("Skipping setSubStatusInactive for savings account {} -- no longer active (already closed by another process)",
|
||||||
|
savingsId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||||
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
||||||
@@ -1687,6 +1696,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
|||||||
@Override
|
@Override
|
||||||
public void setSubStatusDormant(Long savingsId) {
|
public void setSubStatusDormant(Long savingsId) {
|
||||||
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
|
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
|
||||||
|
if (!account.isActive()) {
|
||||||
|
log.warn("Skipping setSubStatusDormant for savings account {} -- no longer active (already closed by another process)",
|
||||||
|
savingsId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
account.setSubStatusDormant();
|
account.setSubStatusDormant();
|
||||||
this.savingAccountRepositoryWrapper.saveAndFlush(account);
|
this.savingAccountRepositoryWrapper.saveAndFlush(account);
|
||||||
}
|
}
|
||||||
@@ -1694,6 +1708,16 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
|||||||
@Override
|
@Override
|
||||||
public void escheat(Long savingsId) {
|
public void escheat(Long savingsId) {
|
||||||
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
|
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
|
||||||
|
// The dormant-accounts batch selects escheat-eligible ids from a snapshot query, so an
|
||||||
|
// account can legitimately close through another path (e.g. deposit maturity) in the gap
|
||||||
|
// between that selection and this call. escheat() unconditionally forces status/sub-status
|
||||||
|
// to CLOSED/ESCHEAT and re-derives accounting off the account's full transaction list, so
|
||||||
|
// running it against an already-closed account both mislabels the closure reason and
|
||||||
|
// re-posts journal entries for transactions that were already posted by the real closure.
|
||||||
|
if (!account.isActive()) {
|
||||||
|
log.warn("Skipping escheat for savings account {} -- no longer active (already closed by another process)", savingsId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||||
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
||||||
|
|||||||
+23
-12
@@ -76,6 +76,7 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
// for account close
|
// for account close
|
||||||
private final Collection<EnumOptionData> onAccountClosureOptions;
|
private final Collection<EnumOptionData> onAccountClosureOptions;
|
||||||
private final Collection<PaymentTypeData> paymentTypeOptions;
|
private final Collection<PaymentTypeData> paymentTypeOptions;
|
||||||
|
private final Collection<EnumOptionData> maturityInstructionOptions;
|
||||||
|
|
||||||
// import fields
|
// import fields
|
||||||
private transient Integer rowIndex;
|
private transient Integer rowIndex;
|
||||||
@@ -364,6 +365,7 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
this.savingsAccounts = null;
|
this.savingsAccounts = null;
|
||||||
this.onAccountClosureOptions = null;
|
this.onAccountClosureOptions = null;
|
||||||
this.paymentTypeOptions = null;
|
this.paymentTypeOptions = null;
|
||||||
|
this.maturityInstructionOptions = null;
|
||||||
this.rowIndex = rowIndex;
|
this.rowIndex = rowIndex;
|
||||||
this.dateFormat = dateFormat;
|
this.dateFormat = dateFormat;
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
@@ -394,6 +396,7 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
final Collection<SavingsAccountData> savingsAccountDatas = null;
|
final Collection<SavingsAccountData> savingsAccountDatas = null;
|
||||||
final Integer recurringFrequency = null;
|
final Integer recurringFrequency = null;
|
||||||
final EnumOptionData recurringFrequencyType = null;
|
final EnumOptionData recurringFrequencyType = null;
|
||||||
|
final Collection<EnumOptionData> maturityInstructionOptions = null;
|
||||||
|
|
||||||
return new RecurringDepositAccountData(depositAccountData.id, depositAccountData.accountNo, depositAccountData.externalId,
|
return new RecurringDepositAccountData(depositAccountData.id, depositAccountData.accountNo, depositAccountData.externalId,
|
||||||
depositAccountData.groupId, depositAccountData.groupName, depositAccountData.clientId, depositAccountData.clientName,
|
depositAccountData.groupId, depositAccountData.groupName, depositAccountData.clientId, depositAccountData.clientName,
|
||||||
@@ -416,7 +419,7 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, expectedFirstDepositOnDate,
|
onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, expectedFirstDepositOnDate,
|
||||||
totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments,
|
totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments,
|
||||||
isCalendarInherited, recurringFrequency, recurringFrequencyType, depositAccountData.withHoldTax,
|
isCalendarInherited, recurringFrequency, recurringFrequencyType, depositAccountData.withHoldTax,
|
||||||
depositAccountData.taxGroup);
|
depositAccountData.taxGroup, maturityInstructionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RecurringDepositAccountData withInterestChartAndRecurringDetails(final RecurringDepositAccountData account,
|
public static RecurringDepositAccountData withInterestChartAndRecurringDetails(final RecurringDepositAccountData account,
|
||||||
@@ -441,7 +444,7 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
|
account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
|
||||||
account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
|
account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
|
||||||
account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, recurringFrequency, recurringFrequencyType,
|
account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, recurringFrequency, recurringFrequencyType,
|
||||||
account.withHoldTax, account.taxGroup);
|
account.withHoldTax, account.taxGroup, account.maturityInstructionOptions);
|
||||||
result.copyCbsFieldsFrom(account);
|
result.copyCbsFieldsFrom(account);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -463,11 +466,13 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
|
|
||||||
final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
|
final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions = null;
|
||||||
final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
|
final Collection<EnumOptionData> periodFrequencyTypeOptions = null;
|
||||||
|
final Collection<SavingsAccountData> savingsAccountDatas = account.savingsAccounts;
|
||||||
|
final Collection<EnumOptionData> maturityInstructionOptions = account.maturityInstructionOptions;
|
||||||
|
|
||||||
return withTemplateOptions(account, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
|
return withTemplateOptions(account, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions,
|
||||||
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
|
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
|
||||||
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions, charges, chargeOptions,
|
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions, charges, chargeOptions,
|
||||||
preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions);
|
preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, savingsAccountDatas, maturityInstructionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
final RecurringDepositAccountData result = new RecurringDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
|
final RecurringDepositAccountData result = new RecurringDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
|
||||||
@@ -486,10 +491,10 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
|
account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
|
||||||
account.depositPeriod, account.depositPeriodFrequency, account.mandatoryRecommendedDepositAmount,
|
account.depositPeriod, account.depositPeriodFrequency, account.mandatoryRecommendedDepositAmount,
|
||||||
template.periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions,
|
template.periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions,
|
||||||
account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
|
account.paymentTypeOptions, template.savingsAccounts, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
|
||||||
account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
|
account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
|
||||||
account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency,
|
account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency,
|
||||||
account.recurringFrequencyType, account.withHoldTax, account.taxGroup);
|
account.recurringFrequencyType, account.withHoldTax, account.taxGroup, account.maturityInstructionOptions);
|
||||||
result.copyCbsFieldsFrom(account);
|
result.copyCbsFieldsFrom(account);
|
||||||
// Merge accountTypeOptions and customerSegmentOptions from template
|
// Merge accountTypeOptions and customerSegmentOptions from template
|
||||||
if (template.getAccountTypeOptions() != null) result.setAccountTypeOptions(template.getAccountTypeOptions());
|
if (template.getAccountTypeOptions() != null) result.setAccountTypeOptions(template.getAccountTypeOptions());
|
||||||
@@ -507,7 +512,8 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
|
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
|
||||||
final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges,
|
final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges,
|
||||||
final Collection<ChargeData> chargeOptions, final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
|
final Collection<ChargeData> chargeOptions, final Collection<EnumOptionData> preClosurePenalInterestOnTypeOptions,
|
||||||
final Collection<EnumOptionData> periodFrequencyTypeOptions) {
|
final Collection<EnumOptionData> periodFrequencyTypeOptions, final Collection<SavingsAccountData> savingsAccountDatas,
|
||||||
|
final Collection<EnumOptionData> maturityInstructionOptions) {
|
||||||
|
|
||||||
final RecurringDepositAccountData r2 = new RecurringDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
|
final RecurringDepositAccountData r2 = new RecurringDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
|
||||||
account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
|
account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId,
|
||||||
@@ -524,10 +530,10 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
|
account.inMultiplesOfDepositTermType, account.depositAmount, account.maturityAmount, account.maturityDate,
|
||||||
account.depositPeriod, account.depositPeriodFrequency, account.mandatoryRecommendedDepositAmount,
|
account.depositPeriod, account.depositPeriodFrequency, account.mandatoryRecommendedDepositAmount,
|
||||||
periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions,
|
periodFrequencyTypeOptions, account.depositType, account.onAccountClosure, account.onAccountClosureOptions,
|
||||||
account.paymentTypeOptions, account.savingsAccounts, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
|
account.paymentTypeOptions, savingsAccountDatas, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
|
||||||
account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
|
account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
|
||||||
account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency,
|
account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency,
|
||||||
account.recurringFrequencyType, account.withHoldTax, account.taxGroup);
|
account.recurringFrequencyType, account.withHoldTax, account.taxGroup, maturityInstructionOptions);
|
||||||
r2.copyCbsFieldsFrom(account);
|
r2.copyCbsFieldsFrom(account);
|
||||||
return r2;
|
return r2;
|
||||||
}
|
}
|
||||||
@@ -609,6 +615,7 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
final EnumOptionData recurringFrequencyType = null;
|
final EnumOptionData recurringFrequencyType = null;
|
||||||
final boolean withHoldTax = false;
|
final boolean withHoldTax = false;
|
||||||
final TaxGroupData taxGroup = null;
|
final TaxGroupData taxGroup = null;
|
||||||
|
final Collection<EnumOptionData> maturityInstructionOptions = null;
|
||||||
|
|
||||||
return new RecurringDepositAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
return new RecurringDepositAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||||
fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
|
fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
|
||||||
@@ -622,7 +629,8 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, mandatoryRecommendedDepositAmount,
|
depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, mandatoryRecommendedDepositAmount,
|
||||||
periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas,
|
periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas,
|
||||||
expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal,
|
expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal,
|
||||||
adjustAdvanceTowardsFuturePayments, isCalendarInherited, recurringFrequency, recurringFrequencyType, withHoldTax, taxGroup);
|
adjustAdvanceTowardsFuturePayments, isCalendarInherited, recurringFrequency, recurringFrequencyType, withHoldTax, taxGroup,
|
||||||
|
maturityInstructionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RecurringDepositAccountData preClosureDetails(final Long accountId, final BigDecimal maturityAmount,
|
public static RecurringDepositAccountData preClosureDetails(final Long accountId, final BigDecimal maturityAmount,
|
||||||
@@ -700,6 +708,7 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
final EnumOptionData recurringFrequencyType = null;
|
final EnumOptionData recurringFrequencyType = null;
|
||||||
final boolean withHoldTax = false;
|
final boolean withHoldTax = false;
|
||||||
final TaxGroupData taxGroup = null;
|
final TaxGroupData taxGroup = null;
|
||||||
|
final Collection<EnumOptionData> maturityInstructionOptions = null;
|
||||||
|
|
||||||
return new RecurringDepositAccountData(accountId, accountNo, externalId, groupId, groupName, clientId, clientName, productId,
|
return new RecurringDepositAccountData(accountId, accountNo, externalId, groupId, groupName, clientId, clientName, productId,
|
||||||
productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
|
productName, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
|
||||||
@@ -713,7 +722,8 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, mandatoryRecommendedDepositAmount,
|
depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequency, mandatoryRecommendedDepositAmount,
|
||||||
periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas,
|
periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas,
|
||||||
expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal,
|
expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal,
|
||||||
adjustAdvanceTowardsFuturePayments, isCalendarInherited, recurringFrequency, recurringFrequencyType, withHoldTax, taxGroup);
|
adjustAdvanceTowardsFuturePayments, isCalendarInherited, recurringFrequency, recurringFrequencyType, withHoldTax, taxGroup,
|
||||||
|
maturityInstructionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RecurringDepositAccountData withClosureTemplateDetails(final RecurringDepositAccountData account,
|
public static RecurringDepositAccountData withClosureTemplateDetails(final RecurringDepositAccountData account,
|
||||||
@@ -739,7 +749,7 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
paymentTypeOptions, savingsAccountDatas, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
|
paymentTypeOptions, savingsAccountDatas, account.expectedFirstDepositOnDate, account.totalOverdueAmount,
|
||||||
account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
|
account.noOfOverdueInstallments, account.isMandatoryDeposit, account.allowWithdrawal,
|
||||||
account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency,
|
account.adjustAdvanceTowardsFuturePayments, account.isCalendarInherited, account.recurringFrequency,
|
||||||
account.recurringFrequencyType, account.withHoldTax, account.taxGroup);
|
account.recurringFrequencyType, account.withHoldTax, account.taxGroup, account.maturityInstructionOptions);
|
||||||
closureResult.copyCbsFieldsFrom(account);
|
closureResult.copyCbsFieldsFrom(account);
|
||||||
return closureResult;
|
return closureResult;
|
||||||
}
|
}
|
||||||
@@ -774,7 +784,7 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
final BigDecimal totalOverdueAmount, final Integer noOfOverdueInstallments, final boolean isMandatoryDeposit,
|
final BigDecimal totalOverdueAmount, final Integer noOfOverdueInstallments, final boolean isMandatoryDeposit,
|
||||||
final boolean allowWithdrawal, final boolean adjustAdvanceTowardsFuturePayments, final boolean isCalendarInherited,
|
final boolean allowWithdrawal, final boolean adjustAdvanceTowardsFuturePayments, final boolean isCalendarInherited,
|
||||||
final Integer recurringFrequency, final EnumOptionData recurringFrequencyType, final boolean withHoldTax,
|
final Integer recurringFrequency, final EnumOptionData recurringFrequencyType, final boolean withHoldTax,
|
||||||
final TaxGroupData taxGroup) {
|
final TaxGroupData taxGroup, final Collection<EnumOptionData> maturityInstructionOptions) {
|
||||||
|
|
||||||
super(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldofficerId, fieldofficerName,
|
super(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldofficerId, fieldofficerName,
|
||||||
status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType,
|
status, timeline, currency, nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType,
|
||||||
@@ -818,6 +828,7 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
|||||||
// account close template options
|
// account close template options
|
||||||
this.onAccountClosureOptions = onAccountClosureOptions;
|
this.onAccountClosureOptions = onAccountClosureOptions;
|
||||||
this.paymentTypeOptions = paymentTypeOptions;
|
this.paymentTypeOptions = paymentTypeOptions;
|
||||||
|
this.maturityInstructionOptions = maturityInstructionOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+5
@@ -82,6 +82,11 @@ public class SavingsAccountRepositoryWrapper {
|
|||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public boolean existsWithDepositAccountType(final Long savingsId, final DepositAccountType depositAccountType) {
|
||||||
|
return this.repository.findByIdAndDepositAccountType(savingsId, depositAccountType.getValue()) != null;
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public List<SavingsAccount> findSavingAccountByClientId(@Param("clientId") Long clientId) {
|
public List<SavingsAccount> findSavingAccountByClientId(@Param("clientId") Long clientId) {
|
||||||
List<SavingsAccount> accounts = this.repository.findSavingAccountByClientId(clientId);
|
List<SavingsAccount> accounts = this.repository.findSavingAccountByClientId(clientId);
|
||||||
|
|||||||
+3
-3
@@ -172,7 +172,7 @@ public class FixedDepositTest extends IntegrationTest {
|
|||||||
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
|
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
|
||||||
moneyHelperStatic.when(() -> MoneyHelper.getMathContext()).thenReturn(new MathContext(12, RoundingMode.UP));
|
moneyHelperStatic.when(() -> MoneyHelper.getMathContext()).thenReturn(new MathContext(12, RoundingMode.UP));
|
||||||
fixedDepositAccountInterestCalculationServiceImpl = new FixedDepositAccountInterestCalculationServiceImpl(
|
fixedDepositAccountInterestCalculationServiceImpl = new FixedDepositAccountInterestCalculationServiceImpl(
|
||||||
new DepositAccountDataValidator(new FromJsonHelper(), null), new FromJsonHelper());
|
new DepositAccountDataValidator(new FromJsonHelper(), null, null), new FromJsonHelper());
|
||||||
try {
|
try {
|
||||||
HashMap h = fixedDepositAccountInterestCalculationServiceImpl
|
HashMap h = fixedDepositAccountInterestCalculationServiceImpl
|
||||||
.calculateInterest(new JsonQuery(apiRequestBodyAsJson, element, new FromJsonHelper()));
|
.calculateInterest(new JsonQuery(apiRequestBodyAsJson, element, new FromJsonHelper()));
|
||||||
@@ -198,7 +198,7 @@ public class FixedDepositTest extends IntegrationTest {
|
|||||||
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
|
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
|
||||||
moneyHelperStatic.when(() -> MoneyHelper.getMathContext()).thenReturn(new MathContext(12, RoundingMode.UP));
|
moneyHelperStatic.when(() -> MoneyHelper.getMathContext()).thenReturn(new MathContext(12, RoundingMode.UP));
|
||||||
fixedDepositAccountInterestCalculationServiceImpl = new FixedDepositAccountInterestCalculationServiceImpl(
|
fixedDepositAccountInterestCalculationServiceImpl = new FixedDepositAccountInterestCalculationServiceImpl(
|
||||||
new DepositAccountDataValidator(new FromJsonHelper(), null), new FromJsonHelper());
|
new DepositAccountDataValidator(new FromJsonHelper(), null, null), new FromJsonHelper());
|
||||||
try {
|
try {
|
||||||
HashMap h = fixedDepositAccountInterestCalculationServiceImpl
|
HashMap h = fixedDepositAccountInterestCalculationServiceImpl
|
||||||
.calculateInterest(new JsonQuery(apiRequestBodyAsJson, element, new FromJsonHelper()));
|
.calculateInterest(new JsonQuery(apiRequestBodyAsJson, element, new FromJsonHelper()));
|
||||||
@@ -224,7 +224,7 @@ public class FixedDepositTest extends IntegrationTest {
|
|||||||
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
|
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
|
||||||
moneyHelperStatic.when(() -> MoneyHelper.getMathContext()).thenReturn(new MathContext(12, RoundingMode.UP));
|
moneyHelperStatic.when(() -> MoneyHelper.getMathContext()).thenReturn(new MathContext(12, RoundingMode.UP));
|
||||||
fixedDepositAccountInterestCalculationServiceImpl = new FixedDepositAccountInterestCalculationServiceImpl(
|
fixedDepositAccountInterestCalculationServiceImpl = new FixedDepositAccountInterestCalculationServiceImpl(
|
||||||
new DepositAccountDataValidator(new FromJsonHelper(), null), new FromJsonHelper());
|
new DepositAccountDataValidator(new FromJsonHelper(), null, null), new FromJsonHelper());
|
||||||
BigDecimal expectedResult = new BigDecimal("10506.250000");
|
BigDecimal expectedResult = new BigDecimal("10506.250000");
|
||||||
BigDecimal actualResult = new BigDecimal(fixedDepositAccountInterestCalculationServiceImpl
|
BigDecimal actualResult = new BigDecimal(fixedDepositAccountInterestCalculationServiceImpl
|
||||||
.calculateInterest(new JsonQuery(apiRequestBodyAsJson, element, new FromJsonHelper())).get("maturityAmount").toString());
|
.calculateInterest(new JsonQuery(apiRequestBodyAsJson, element, new FromJsonHelper())).get("maturityAmount").toString());
|
||||||
|
|||||||
Reference in New Issue
Block a user