feat: Fixed Deposit - premature close logic, deposit account closure type, transaction validation, domain service and read service updates
This commit is contained in:
+14
-1
@@ -32,7 +32,8 @@ public enum DepositAccountOnClosureType {
|
|||||||
TRANSFER_TO_SAVINGS(200, "depositAccountClosureType.transferToSavings"), //
|
TRANSFER_TO_SAVINGS(200, "depositAccountClosureType.transferToSavings"), //
|
||||||
REINVEST_PRINCIPAL_AND_INTEREST(300, "depositAccountClosureType.reinvestPrincipalAndInterest"), //
|
REINVEST_PRINCIPAL_AND_INTEREST(300, "depositAccountClosureType.reinvestPrincipalAndInterest"), //
|
||||||
REINVEST_PRINCIPAL_ONLY(400, "depositAccountClosureType.reinvestPrincipalOnly"), //
|
REINVEST_PRINCIPAL_ONLY(400, "depositAccountClosureType.reinvestPrincipalOnly"), //
|
||||||
PARTIAL_REINVEST(500, "depositAccountClosureType.partialReinvest"); //
|
PARTIAL_REINVEST(500, "depositAccountClosureType.partialReinvest"), //
|
||||||
|
TRANSFER_TO_GL(600, "depositAccountClosureType.transferToGl"); //
|
||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
private final String code;
|
private final String code;
|
||||||
@@ -53,6 +54,7 @@ public enum DepositAccountOnClosureType {
|
|||||||
case 300 -> REINVEST_PRINCIPAL_AND_INTEREST;
|
case 300 -> REINVEST_PRINCIPAL_AND_INTEREST;
|
||||||
case 400 -> REINVEST_PRINCIPAL_ONLY;
|
case 400 -> REINVEST_PRINCIPAL_ONLY;
|
||||||
case 500 -> PARTIAL_REINVEST;
|
case 500 -> PARTIAL_REINVEST;
|
||||||
|
case 600 -> TRANSFER_TO_GL;
|
||||||
default -> INVALID;
|
default -> INVALID;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -91,6 +93,17 @@ public enum DepositAccountOnClosureType {
|
|||||||
return this.equals(PARTIAL_REINVEST);
|
return this.equals(PARTIAL_REINVEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pays the closure proceeds into a GL account the maker picks, instead of a savings account or cash.
|
||||||
|
* Only the fixed-deposit premature-closure handler implements it -- see
|
||||||
|
* DepositAccountDomainServiceJpa#handleFDAccountPreMatureClosure; every other closure path rejects it
|
||||||
|
* in DepositAccountTransactionDataValidator#validateClosing rather than falling through to the
|
||||||
|
* terminal "pay the balance out in cash" branch.
|
||||||
|
*/
|
||||||
|
public boolean isTransferToGl() {
|
||||||
|
return this.equals(TRANSFER_TO_GL);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: do we really need this?!?
|
// TODO: do we really need this?!?
|
||||||
public static Object[] integerValues() {
|
public static Object[] integerValues() {
|
||||||
return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray();
|
return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray();
|
||||||
|
|||||||
+3
@@ -212,6 +212,9 @@ public final class DepositsApiConstants {
|
|||||||
public static final String onAccountClosureIdParamName = "onAccountClosureId";
|
public static final String onAccountClosureIdParamName = "onAccountClosureId";
|
||||||
public static final String transferDescriptionParamName = "transferDescription";
|
public static final String transferDescriptionParamName = "transferDescription";
|
||||||
public static final String toSavingsAccountIdParamName = "toSavingsAccountId";
|
public static final String toSavingsAccountIdParamName = "toSavingsAccountId";
|
||||||
|
// Destination GL account for DepositAccountOnClosureType.TRANSFER_TO_GL -- the closure proceeds are
|
||||||
|
// credited straight to this GL instead of a savings account or cash.
|
||||||
|
public static final String toGlAccountIdParamName = "toGlAccountId";
|
||||||
public static final String reinvestAmountParamName = "reinvestAmount";
|
public static final String reinvestAmountParamName = "reinvestAmount";
|
||||||
// Lets a maker waive the product's configured pre-closure penalty for one premature closure --
|
// Lets a maker waive the product's configured pre-closure penalty for one premature closure --
|
||||||
// see FixedDepositAccount#waivePreClosurePenaltyForThisRequest for why this is a transient
|
// see FixedDepositAccount#waivePreClosurePenaltyForThisRequest for why this is a transient
|
||||||
|
|||||||
+4
@@ -801,6 +801,10 @@ public final class SavingsEnumerations {
|
|||||||
optionData = new EnumOptionData(DepositAccountOnClosureType.PARTIAL_REINVEST.getValue().longValue(),
|
optionData = new EnumOptionData(DepositAccountOnClosureType.PARTIAL_REINVEST.getValue().longValue(),
|
||||||
DepositAccountOnClosureType.PARTIAL_REINVEST.getCode(), "Partial Re-Invest");
|
DepositAccountOnClosureType.PARTIAL_REINVEST.getCode(), "Partial Re-Invest");
|
||||||
break;
|
break;
|
||||||
|
case TRANSFER_TO_GL:
|
||||||
|
optionData = new EnumOptionData(DepositAccountOnClosureType.TRANSFER_TO_GL.getValue().longValue(),
|
||||||
|
DepositAccountOnClosureType.TRANSFER_TO_GL.getCode(), "Transfer to GL Account");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return optionData;
|
return optionData;
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-1
@@ -78,7 +78,8 @@ public class DepositAccountTransactionDataValidator {
|
|||||||
DepositsApiConstants.noteParamName, onAccountClosureIdParamName, paymentTypeIdParamName,
|
DepositsApiConstants.noteParamName, onAccountClosureIdParamName, paymentTypeIdParamName,
|
||||||
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName,
|
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName,
|
||||||
bankNumberParamName, DepositsApiConstants.transferDescriptionParamName, toSavingsAccountIdParamName,
|
bankNumberParamName, DepositsApiConstants.transferDescriptionParamName, toSavingsAccountIdParamName,
|
||||||
DepositsApiConstants.waivePreClosurePenaltyParamName, DepositsApiConstants.prematureClosureReasonIdParamName));
|
DepositsApiConstants.toGlAccountIdParamName, DepositsApiConstants.waivePreClosurePenaltyParamName,
|
||||||
|
DepositsApiConstants.prematureClosureReasonIdParamName));
|
||||||
|
|
||||||
private static final Set<String> DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
|
private static final Set<String> DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
|
||||||
DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName,
|
DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName,
|
||||||
@@ -247,6 +248,25 @@ public class DepositAccountTransactionDataValidator {
|
|||||||
} else if (accountOnClosureType.isReinvest() && isPreMatureClose) {
|
} else if (accountOnClosureType.isReinvest() && isPreMatureClose) {
|
||||||
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId)
|
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId)
|
||||||
.failWithCode("reinvest.not.allowed", "Re-Invest is not supported for account pre mature close");
|
.failWithCode("reinvest.not.allowed", "Re-Invest is not supported for account pre mature close");
|
||||||
|
} else if (accountOnClosureType.isTransferToGl()) {
|
||||||
|
// Only the FD premature-closure handler books the GL leg. Offering it anywhere else would
|
||||||
|
// fall through to that handler's terminal "withdraw the whole balance" case and silently pay
|
||||||
|
// the deposit out in cash, which is exactly what the partial.reinvest guard above prevents.
|
||||||
|
if (!isPreMatureClose || !DepositAccountType.FIXED_DEPOSIT.equals(depositAccountType)) {
|
||||||
|
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId).failWithCode(
|
||||||
|
"transfer.to.gl.not.allowed",
|
||||||
|
"Transfer to a GL account is only supported for fixed-deposit premature closure");
|
||||||
|
}
|
||||||
|
final Long toGlAccountId = this.fromApiJsonHelper.extractLongNamed(DepositsApiConstants.toGlAccountIdParamName, element);
|
||||||
|
baseDataValidator.reset().parameter(DepositsApiConstants.toGlAccountIdParamName).value(toGlAccountId)
|
||||||
|
.cantBeBlankWhenParameterProvidedIs(onAccountClosureIdParamName, accountOnClosureType.getCode());
|
||||||
|
} else if (accountOnClosureType.isWithdarwDeposit() && isPreMatureClose) {
|
||||||
|
// Premature closure proceeds have to be traceable into a savings account, so the cash payout
|
||||||
|
// is not offered in the preview (see DepositAccountPreMatureCalculationPlatformServiceImpl)
|
||||||
|
// and is refused here as well -- otherwise a direct API call could still take the cash route.
|
||||||
|
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId).failWithCode(
|
||||||
|
"withdraw.deposit.not.allowed",
|
||||||
|
"Paying the deposit out in cash is not supported for account pre mature close -- transfer it to a savings account");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+96
@@ -25,6 +25,8 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.toSavin
|
|||||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferDescriptionParamName;
|
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferDescriptionParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.waivePreClosurePenaltyParamName;
|
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.waivePreClosurePenaltyParamName;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -38,6 +40,7 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.accounting.common.AccountingConstants;
|
||||||
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||||
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
|
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
|
||||||
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
|
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
|
||||||
@@ -48,6 +51,7 @@ import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
|||||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||||
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||||
@@ -76,6 +80,7 @@ import org.apache.fineract.portfolio.savings.data.FdInterestRateSlabData;
|
|||||||
import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformService;
|
import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService;
|
import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -96,6 +101,8 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
private final FixedDepositRenewalHistoryRepository fixedDepositRenewalHistoryRepository;
|
private final FixedDepositRenewalHistoryRepository fixedDepositRenewalHistoryRepository;
|
||||||
private final FixedDepositRenewalChartSlabHistoryRepository fixedDepositRenewalChartSlabHistoryRepository;
|
private final FixedDepositRenewalChartSlabHistoryRepository fixedDepositRenewalChartSlabHistoryRepository;
|
||||||
private final FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService;
|
private final FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService;
|
||||||
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -846,6 +853,20 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
toSavingsAccount, account, isRegularTransaction, isExceptionForBalanceCheck);
|
toSavingsAccount, account, isRegularTransaction, isExceptionForBalanceCheck);
|
||||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||||
updateAlreadyPostedTransactions(existingTransactionIds, account);
|
updateAlreadyPostedTransactions(existingTransactionIds, account);
|
||||||
|
} else if (closureType.isTransferToGl()) {
|
||||||
|
final Long toGlAccountId = command.longValueOfParameterNamed(DepositsApiConstants.toGlAccountIdParamName);
|
||||||
|
final BigDecimal proceeds = account.getAccountBalance();
|
||||||
|
// Declared as an account transfer so the withdrawal's own entry credits the Liability Transfer
|
||||||
|
// account rather than cash/vault -- nothing is paid over the counter here. The offset below then
|
||||||
|
// moves it out of there into the GL the maker picked, leaving Liability Transfer at nil and the
|
||||||
|
// net posting as Dr Savings Control / Cr chosen GL.
|
||||||
|
final boolean isInternalTransfer = true;
|
||||||
|
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, proceeds, paymentDetail, false,
|
||||||
|
isRegularTransaction, isInternalTransfer);
|
||||||
|
savingsTransactionId = withdrawal.getId();
|
||||||
|
updateAlreadyPostedTransactions(existingTransactionIds, account);
|
||||||
|
postClosureProceedsToGlAccount(account, toGlAccountId, proceeds, closedDate,
|
||||||
|
command.stringValueOfParameterNamed(transferDescriptionParamName));
|
||||||
} else {
|
} else {
|
||||||
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
|
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
|
||||||
paymentDetail, false, isRegularTransaction);
|
paymentDetail, false, isRegularTransaction);
|
||||||
@@ -863,6 +884,81 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
return savingsTransactionId;
|
return savingsTransactionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a premature closure's proceeds out of Liability Transfer and into the GL account the maker picked:
|
||||||
|
* Dr the Liability Transfer account / Cr that GL account.
|
||||||
|
* <p>
|
||||||
|
* The withdrawal booked immediately before this was declared an account transfer, so the accounting engine
|
||||||
|
* has already credited Liability Transfer for the same amount -- this entry clears it, leaving the pair
|
||||||
|
* netting to Dr Savings Control / Cr the chosen GL, and Liability Transfer back at nil. Posted through the real
|
||||||
|
* createJournalEntry command rather than written straight to the ledger, so it reverses, reports and
|
||||||
|
* reconciles exactly like any other manual entry.
|
||||||
|
*/
|
||||||
|
private void postClosureProceedsToGlAccount(final SavingsAccount account, final Long toGlAccountId, final BigDecimal amount,
|
||||||
|
final LocalDate closedDate, final String description) {
|
||||||
|
|
||||||
|
final Long suspenseGlAccountId = liabilityTransferGlAccountId();
|
||||||
|
|
||||||
|
final JsonObject debitEntry = new JsonObject();
|
||||||
|
debitEntry.addProperty("glAccountId", suspenseGlAccountId);
|
||||||
|
debitEntry.addProperty("amount", amount);
|
||||||
|
final JsonArray debits = new JsonArray();
|
||||||
|
debits.add(debitEntry);
|
||||||
|
|
||||||
|
final JsonObject creditEntry = new JsonObject();
|
||||||
|
creditEntry.addProperty("glAccountId", toGlAccountId);
|
||||||
|
creditEntry.addProperty("amount", amount);
|
||||||
|
final JsonArray credits = new JsonArray();
|
||||||
|
credits.add(creditEntry);
|
||||||
|
|
||||||
|
final StringBuilder comments = new StringBuilder("Premature closure of fixed deposit ").append(account.getAccountNumber());
|
||||||
|
if (description != null && !description.isBlank()) {
|
||||||
|
comments.append(" -- ").append(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String dateFormat = "dd MMMM yyyy";
|
||||||
|
final JsonObject payload = new JsonObject();
|
||||||
|
payload.addProperty("officeId", account.officeId());
|
||||||
|
payload.addProperty("transactionDate", closedDate.format(DateTimeFormatter.ofPattern(dateFormat, Locale.ENGLISH)));
|
||||||
|
payload.addProperty("dateFormat", dateFormat);
|
||||||
|
payload.addProperty("locale", "en");
|
||||||
|
payload.addProperty("currencyCode", account.getCurrency().getCode());
|
||||||
|
payload.addProperty("comments", comments.toString());
|
||||||
|
payload.add("debits", debits);
|
||||||
|
payload.add("credits", credits);
|
||||||
|
|
||||||
|
final String json = payload.toString();
|
||||||
|
final JsonCommand journalEntryCommand = JsonCommand.from(json, this.fromApiJsonHelper.parse(json), this.fromApiJsonHelper, null,
|
||||||
|
null, null, null, null, null, null, null, null, null, null, null, null, null);
|
||||||
|
this.journalEntryWritePlatformService.createJournalEntry(journalEntryCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GL account the withdrawal above credited, and therefore the exact account this entry has to debit
|
||||||
|
* to clear it.
|
||||||
|
* <p>
|
||||||
|
* It is the organisation-wide Liability Transfer financial activity, NOT the product's own Transfers In
|
||||||
|
* Suspense mapping: {@code CashBasedAccountingProcessorForSavings} books an account-transfer withdrawal
|
||||||
|
* as Dr Savings Control / Cr {@code FinancialActivity.LIABILITY_TRANSFER}. Debiting the product mapping
|
||||||
|
* instead leaves the credit standing in Liability Transfer for ever while the two legs of this entry
|
||||||
|
* collapse onto whichever account that mapping names.
|
||||||
|
* <p>
|
||||||
|
* Refusing outright when it is unmapped is deliberate: silently picking another account would put the
|
||||||
|
* closure's money somewhere nobody chose.
|
||||||
|
*/
|
||||||
|
private Long liabilityTransferGlAccountId() {
|
||||||
|
final List<Long> glAccountIds = this.jdbcTemplate.queryForList(
|
||||||
|
"SELECT gl_account_id FROM acc_gl_financial_activity_account WHERE financial_activity_type = ? ORDER BY id DESC",
|
||||||
|
Long.class, AccountingConstants.FinancialActivity.LIABILITY_TRANSFER.getValue());
|
||||||
|
if (glAccountIds.isEmpty()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.liability.transfer.financial.activity.not.mapped",
|
||||||
|
"No Liability Transfer account is mapped under Accounting > Financial Activity Mappings, so the closure"
|
||||||
|
+ " proceeds cannot be routed to a GL account. Map it first.",
|
||||||
|
AccountingConstants.FinancialActivity.LIABILITY_TRANSFER.getValue());
|
||||||
|
}
|
||||||
|
return glAccountIds.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A staff-triggered, always-available alternative to Premature Close (Active-only, no reinvest) and Close
|
* A staff-triggered, always-available alternative to Premature Close (Active-only, no reinvest) and Close
|
||||||
* (Matured-only): works on an FD in either state, and adds a Partial Reinvest option neither of those two support
|
* (Matured-only): works on an FD in either state, and adds a Partial Reinvest option neither of those two support
|
||||||
|
|||||||
+11
-3
@@ -946,13 +946,21 @@ public class FixedDepositAccount extends SavingsAccount {
|
|||||||
* the term restarted belongs to a term that has already been settled.
|
* the term restarted belongs to a term that has already been settled.
|
||||||
*/
|
*/
|
||||||
private Money totalInterestPosted() {
|
private Money totalInterestPosted() {
|
||||||
|
final boolean renewedTerm = isRenewedTerm();
|
||||||
final LocalDate currentTermStartDate = depositStartDate();
|
final LocalDate currentTermStartDate = depositStartDate();
|
||||||
Money interestPostedToDate = Money.zero(this.currency);
|
Money interestPostedToDate = Money.zero(this.currency);
|
||||||
for (final SavingsAccountTransaction transaction : this.transactions) {
|
for (final SavingsAccountTransaction transaction : this.transactions) {
|
||||||
if (transaction.isInterestPostingAndNotReversed()
|
if (!transaction.isInterestPostingAndNotReversed()) {
|
||||||
&& !DateUtils.isBefore(transaction.getTransactionDate(), currentTermStartDate)) {
|
continue;
|
||||||
interestPostedToDate = interestPostedToDate.plus(transaction.getAmount(currency));
|
|
||||||
}
|
}
|
||||||
|
// Strictly after, and matching transactionsForCurrentTermInterestCalculation() exactly: the
|
||||||
|
// previous term's closing interest posting is dated the SAME day the new term starts, so an
|
||||||
|
// inclusive bound still counts it here while the posting-period walk excludes it -- leaving
|
||||||
|
// the old term's entire interest subtracted from a maturity amount it was never part of.
|
||||||
|
if (renewedTerm && !DateUtils.isAfter(transaction.getTransactionDate(), currentTermStartDate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
interestPostedToDate = interestPostedToDate.plus(transaction.getAmount(currency));
|
||||||
}
|
}
|
||||||
|
|
||||||
return interestPostedToDate;
|
return interestPostedToDate;
|
||||||
|
|||||||
+11
-3
@@ -70,9 +70,17 @@ public class DepositAccountPreMatureCalculationPlatformServiceImpl implements De
|
|||||||
final SavingsAccount account = this.depositAccountAssembler.assembleFrom(accountId, depositAccountType);
|
final SavingsAccount account = this.depositAccountAssembler.assembleFrom(accountId, depositAccountType);
|
||||||
|
|
||||||
DepositAccountData accountData = null;
|
DepositAccountData accountData = null;
|
||||||
Collection<EnumOptionData> onAccountClosureOptions = SavingsEnumerations
|
// Premature closure proceeds must stay traceable -- into the customer's savings account, or (fixed
|
||||||
.depositAccountOnClosureType(new DepositAccountOnClosureType[] { DepositAccountOnClosureType.WITHDRAW_DEPOSIT,
|
// deposits only) straight to a GL account the maker picks. Paying a broken deposit out over the
|
||||||
DepositAccountOnClosureType.TRANSFER_TO_SAVINGS });
|
// counter in cash is not offered: WITHDRAW_DEPOSIT is deliberately absent here and rejected outright
|
||||||
|
// by DepositAccountTransactionDataValidator#validateClosing, so a direct API call cannot take it
|
||||||
|
// either. TRANSFER_TO_GL is offered only for fixed deposits because only
|
||||||
|
// DepositAccountDomainServiceJpa#handleFDAccountPreMatureClosure books its GL leg.
|
||||||
|
final DepositAccountOnClosureType[] closureTypes = depositAccountType.isFixedDeposit()
|
||||||
|
? new DepositAccountOnClosureType[] { DepositAccountOnClosureType.TRANSFER_TO_SAVINGS,
|
||||||
|
DepositAccountOnClosureType.TRANSFER_TO_GL }
|
||||||
|
: new DepositAccountOnClosureType[] { DepositAccountOnClosureType.TRANSFER_TO_SAVINGS };
|
||||||
|
Collection<EnumOptionData> onAccountClosureOptions = SavingsEnumerations.depositAccountOnClosureType(closureTypes);
|
||||||
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||||
final Collection<SavingsAccountData> savingsAccountDatas = this.savingsAccountReadPlatformService
|
final Collection<SavingsAccountData> savingsAccountDatas = this.savingsAccountReadPlatformService
|
||||||
.retrieveActiveForLookup(account.clientId(), DepositAccountType.SAVINGS_DEPOSIT);
|
.retrieveActiveForLookup(account.clientId(), DepositAccountType.SAVINGS_DEPOSIT);
|
||||||
|
|||||||
+5
-1
@@ -543,9 +543,13 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
|||||||
|
|
||||||
DepositAccountData account = this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositAccountMapper, accountId,
|
DepositAccountData account = this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositAccountMapper, accountId,
|
||||||
depositAccountType.getValue());
|
depositAccountType.getValue());
|
||||||
|
// TRANSFER_TO_GL is excluded for the same reason PARTIAL_REINVEST is filtered above: only one
|
||||||
|
// handler implements it (fixed-deposit PREMATURE closure, which builds its own option list in
|
||||||
|
// DepositAccountPreMatureCalculationPlatformServiceImpl). Offering it on this template -- the
|
||||||
|
// normal Close and Payment screens -- would only get it refused by validateClosing.
|
||||||
Collection<EnumOptionData> onAccountClosureOptions = SavingsEnumerations
|
Collection<EnumOptionData> onAccountClosureOptions = SavingsEnumerations
|
||||||
.depositAccountOnClosureType(Arrays.stream(DepositAccountOnClosureType.values())
|
.depositAccountOnClosureType(Arrays.stream(DepositAccountOnClosureType.values())
|
||||||
.filter(type -> includePartialReinvest || !type.isPartialReinvest())
|
.filter(type -> includePartialReinvest || !type.isPartialReinvest()).filter(type -> !type.isTransferToGl())
|
||||||
.toArray(DepositAccountOnClosureType[]::new));
|
.toArray(DepositAccountOnClosureType[]::new));
|
||||||
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||||
final Collection<SavingsAccountData> savingsAccountDatas = this.savingsAccountReadPlatformService
|
final Collection<SavingsAccountData> savingsAccountDatas = this.savingsAccountReadPlatformService
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ fineract.security.oauth2.client.registrations.frontend-client.require-authorizat
|
|||||||
fineract.tenant.host=${FINERACT_DEFAULT_TENANTDB_HOSTNAME:localhost}
|
fineract.tenant.host=${FINERACT_DEFAULT_TENANTDB_HOSTNAME:localhost}
|
||||||
fineract.tenant.port=${FINERACT_DEFAULT_TENANTDB_PORT:5432}
|
fineract.tenant.port=${FINERACT_DEFAULT_TENANTDB_PORT:5432}
|
||||||
fineract.tenant.username=${FINERACT_DEFAULT_TENANTDB_UID:postgres}
|
fineract.tenant.username=${FINERACT_DEFAULT_TENANTDB_UID:postgres}
|
||||||
fineract.tenant.password=${FINERACT_DEFAULT_TENANTDB_PWD:Keshav@898966}
|
fineract.tenant.password=${FINERACT_DEFAULT_TENANTDB_PWD:313863}
|
||||||
fineract.tenant.parameters=${FINERACT_DEFAULT_TENANTDB_CONN_PARAMS:}
|
fineract.tenant.parameters=${FINERACT_DEFAULT_TENANTDB_CONN_PARAMS:}
|
||||||
fineract.tenant.timezone=${FINERACT_DEFAULT_TENANTDB_TIMEZONE:Asia/Kolkata}
|
fineract.tenant.timezone=${FINERACT_DEFAULT_TENANTDB_TIMEZONE:Asia/Kolkata}
|
||||||
fineract.tenant.identifier=${FINERACT_DEFAULT_TENANTDB_IDENTIFIER:default}
|
fineract.tenant.identifier=${FINERACT_DEFAULT_TENANTDB_IDENTIFIER:default}
|
||||||
@@ -415,7 +415,7 @@ server.tomcat.mbeanregistry.enabled=${FINERACT_SERVER_TOMCAT_MBEANREGISTRY_ENABL
|
|||||||
spring.datasource.hikari.driverClassName=${FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME:org.postgresql.Driver}
|
spring.datasource.hikari.driverClassName=${FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME:org.postgresql.Driver}
|
||||||
spring.datasource.hikari.jdbcUrl=${FINERACT_HIKARI_JDBC_URL:jdbc:postgresql://localhost:5432/fineract_tenants}
|
spring.datasource.hikari.jdbcUrl=${FINERACT_HIKARI_JDBC_URL:jdbc:postgresql://localhost:5432/fineract_tenants}
|
||||||
spring.datasource.hikari.username=${FINERACT_HIKARI_USERNAME:postgres}
|
spring.datasource.hikari.username=${FINERACT_HIKARI_USERNAME:postgres}
|
||||||
spring.datasource.hikari.password=${FINERACT_HIKARI_PASSWORD:Keshav@898966}
|
spring.datasource.hikari.password=${FINERACT_HIKARI_PASSWORD:313863}
|
||||||
spring.datasource.hikari.minimumIdle=${FINERACT_HIKARI_MINIMUM_IDLE:3}
|
spring.datasource.hikari.minimumIdle=${FINERACT_HIKARI_MINIMUM_IDLE:3}
|
||||||
spring.datasource.hikari.maximumPoolSize=${FINERACT_HIKARI_MAXIMUM_POOL_SIZE:10}
|
spring.datasource.hikari.maximumPoolSize=${FINERACT_HIKARI_MAXIMUM_POOL_SIZE:10}
|
||||||
spring.datasource.hikari.idleTimeout=${FINERACT_HIKARI_IDLE_TIMEOUT:60000}
|
spring.datasource.hikari.idleTimeout=${FINERACT_HIKARI_IDLE_TIMEOUT:60000}
|
||||||
|
|||||||
Reference in New Issue
Block a user