diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnClosureType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnClosureType.java index c4939917b..03a4763dc 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnClosureType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositAccountOnClosureType.java @@ -32,7 +32,8 @@ public enum DepositAccountOnClosureType { TRANSFER_TO_SAVINGS(200, "depositAccountClosureType.transferToSavings"), // REINVEST_PRINCIPAL_AND_INTEREST(300, "depositAccountClosureType.reinvestPrincipalAndInterest"), // 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 String code; @@ -53,6 +54,7 @@ public enum DepositAccountOnClosureType { case 300 -> REINVEST_PRINCIPAL_AND_INTEREST; case 400 -> REINVEST_PRINCIPAL_ONLY; case 500 -> PARTIAL_REINVEST; + case 600 -> TRANSFER_TO_GL; default -> INVALID; }; } @@ -91,6 +93,17 @@ public enum DepositAccountOnClosureType { 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?!? public static Object[] integerValues() { return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray(); diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java index 2ca00fc8c..fd2b66e0d 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java @@ -212,6 +212,9 @@ public final class DepositsApiConstants { public static final String onAccountClosureIdParamName = "onAccountClosureId"; public static final String transferDescriptionParamName = "transferDescription"; 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"; // Lets a maker waive the product's configured pre-closure penalty for one premature closure -- // see FixedDepositAccount#waivePreClosurePenaltyForThisRequest for why this is a transient diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java index 3ed32ff1c..6c4a4bdc1 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java @@ -801,6 +801,10 @@ public final class SavingsEnumerations { optionData = new EnumOptionData(DepositAccountOnClosureType.PARTIAL_REINVEST.getValue().longValue(), DepositAccountOnClosureType.PARTIAL_REINVEST.getCode(), "Partial Re-Invest"); 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; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountTransactionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountTransactionDataValidator.java index 2aeaa146d..a841f1b1b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountTransactionDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountTransactionDataValidator.java @@ -78,7 +78,8 @@ public class DepositAccountTransactionDataValidator { DepositsApiConstants.noteParamName, onAccountClosureIdParamName, paymentTypeIdParamName, transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName, DepositsApiConstants.transferDescriptionParamName, toSavingsAccountIdParamName, - DepositsApiConstants.waivePreClosurePenaltyParamName, DepositsApiConstants.prematureClosureReasonIdParamName)); + DepositsApiConstants.toGlAccountIdParamName, DepositsApiConstants.waivePreClosurePenaltyParamName, + DepositsApiConstants.prematureClosureReasonIdParamName)); private static final Set DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList( DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName, @@ -247,6 +248,25 @@ public class DepositAccountTransactionDataValidator { } else if (accountOnClosureType.isReinvest() && isPreMatureClose) { baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId) .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"); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java index 6e4102a71..80ab253d5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java @@ -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.waivePreClosurePenaltyParamName; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import java.math.BigDecimal; import java.math.MathContext; import java.time.LocalDate; @@ -38,6 +40,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import lombok.RequiredArgsConstructor; +import org.apache.fineract.accounting.common.AccountingConstants; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat; 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.service.DateUtils; 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.data.AccountTransferDTO; 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.SavingsAccountDomainService; import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -96,6 +101,8 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi private final FixedDepositRenewalHistoryRepository fixedDepositRenewalHistoryRepository; private final FixedDepositRenewalChartSlabHistoryRepository fixedDepositRenewalChartSlabHistoryRepository; private final FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService; + private final FromJsonHelper fromApiJsonHelper; + private final JdbcTemplate jdbcTemplate; @Transactional @Override @@ -846,6 +853,20 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi toSavingsAccount, account, isRegularTransaction, isExceptionForBalanceCheck); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); 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 { final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(), paymentDetail, false, isRegularTransaction); @@ -863,6 +884,81 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 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 * (Matured-only): works on an FD in either state, and adds a Partial Reinvest option neither of those two support diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java index 788ade745..b43d758ce 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java @@ -946,13 +946,21 @@ public class FixedDepositAccount extends SavingsAccount { * the term restarted belongs to a term that has already been settled. */ private Money totalInterestPosted() { + final boolean renewedTerm = isRenewedTerm(); final LocalDate currentTermStartDate = depositStartDate(); Money interestPostedToDate = Money.zero(this.currency); for (final SavingsAccountTransaction transaction : this.transactions) { - if (transaction.isInterestPostingAndNotReversed() - && !DateUtils.isBefore(transaction.getTransactionDate(), currentTermStartDate)) { - interestPostedToDate = interestPostedToDate.plus(transaction.getAmount(currency)); + if (!transaction.isInterestPostingAndNotReversed()) { + continue; } + // 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; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountPreMatureCalculationPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountPreMatureCalculationPlatformServiceImpl.java index 822d6bcb8..b8df20388 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountPreMatureCalculationPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountPreMatureCalculationPlatformServiceImpl.java @@ -70,9 +70,17 @@ public class DepositAccountPreMatureCalculationPlatformServiceImpl implements De final SavingsAccount account = this.depositAccountAssembler.assembleFrom(accountId, depositAccountType); DepositAccountData accountData = null; - Collection onAccountClosureOptions = SavingsEnumerations - .depositAccountOnClosureType(new DepositAccountOnClosureType[] { DepositAccountOnClosureType.WITHDRAW_DEPOSIT, - DepositAccountOnClosureType.TRANSFER_TO_SAVINGS }); + // Premature closure proceeds must stay traceable -- into the customer's savings account, or (fixed + // deposits only) straight to a GL account the maker picks. Paying a broken deposit out over the + // 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 onAccountClosureOptions = SavingsEnumerations.depositAccountOnClosureType(closureTypes); final Collection paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); final Collection savingsAccountDatas = this.savingsAccountReadPlatformService .retrieveActiveForLookup(account.clientId(), DepositAccountType.SAVINGS_DEPOSIT); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java index 421ed87ad..914fd86b1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java @@ -543,9 +543,13 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead DepositAccountData account = this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositAccountMapper, accountId, 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 onAccountClosureOptions = SavingsEnumerations .depositAccountOnClosureType(Arrays.stream(DepositAccountOnClosureType.values()) - .filter(type -> includePartialReinvest || !type.isPartialReinvest()) + .filter(type -> includePartialReinvest || !type.isPartialReinvest()).filter(type -> !type.isTransferToGl()) .toArray(DepositAccountOnClosureType[]::new)); final Collection paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); final Collection savingsAccountDatas = this.savingsAccountReadPlatformService diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 020cdbd2f..108e7421a 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -46,7 +46,7 @@ fineract.security.oauth2.client.registrations.frontend-client.require-authorizat fineract.tenant.host=${FINERACT_DEFAULT_TENANTDB_HOSTNAME:localhost} fineract.tenant.port=${FINERACT_DEFAULT_TENANTDB_PORT:5432} 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.timezone=${FINERACT_DEFAULT_TENANTDB_TIMEZONE:Asia/Kolkata} 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.jdbcUrl=${FINERACT_HIKARI_JDBC_URL:jdbc:postgresql://localhost:5432/fineract_tenants} 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.maximumPoolSize=${FINERACT_HIKARI_MAXIMUM_POOL_SIZE:10} spring.datasource.hikari.idleTimeout=${FINERACT_HIKARI_IDLE_TIMEOUT:60000}