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"), //
|
||||
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();
|
||||
|
||||
+3
@@ -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
|
||||
|
||||
+4
@@ -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;
|
||||
}
|
||||
|
||||
+21
-1
@@ -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<String> 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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+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.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.
|
||||
* <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
|
||||
* (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.
|
||||
*/
|
||||
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;
|
||||
|
||||
+11
-3
@@ -70,9 +70,17 @@ public class DepositAccountPreMatureCalculationPlatformServiceImpl implements De
|
||||
final SavingsAccount account = this.depositAccountAssembler.assembleFrom(accountId, depositAccountType);
|
||||
|
||||
DepositAccountData accountData = null;
|
||||
Collection<EnumOptionData> 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<EnumOptionData> onAccountClosureOptions = SavingsEnumerations.depositAccountOnClosureType(closureTypes);
|
||||
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||
final Collection<SavingsAccountData> savingsAccountDatas = this.savingsAccountReadPlatformService
|
||||
.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,
|
||||
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
|
||||
.depositAccountOnClosureType(Arrays.stream(DepositAccountOnClosureType.values())
|
||||
.filter(type -> includePartialReinvest || !type.isPartialReinvest())
|
||||
.filter(type -> includePartialReinvest || !type.isPartialReinvest()).filter(type -> !type.isTransferToGl())
|
||||
.toArray(DepositAccountOnClosureType[]::new));
|
||||
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||
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.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}
|
||||
|
||||
Reference in New Issue
Block a user