Skip to content

Commit 07d7590

Browse files
committed
FINERACT-2471: Implement 'Force Debit' functionality for Savings Accounts with Configurable Limits
1 parent ce3c9cc commit 07d7590

26 files changed

Lines changed: 506 additions & 37 deletions

File tree

fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3877,4 +3877,13 @@ public CommandWrapperBuilder detachLoanOriginator(final Long loanId, final Long
38773877
this.href = "/loans/" + loanId + "/originators/" + originatorId;
38783878
return this;
38793879
}
3880+
3881+
public CommandWrapperBuilder savingsAccountForceWithdrawal(final Long accountId) {
3882+
this.actionName = "FORCE_WITHDRAWAL";
3883+
this.entityName = "SAVINGSACCOUNT";
3884+
this.entityId = accountId;
3885+
this.savingsId = accountId;
3886+
this.href = "/savingsaccounts/" + accountId;
3887+
return this;
3888+
}
38803889
}

fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public final class GlobalConfigurationConstants {
8181
public static final String ALLOWED_LOAN_STATUSES_OF_DELAYED_SETTLEMENT_FOR_EXTERNAL_ASSET_TRANSFER = "allowed-loan-statuses-of-delayed-settlement-for-external-asset-transfer";
8282
public static final String ENABLE_ORIGINATOR_CREATION_DURING_LOAN_APPLICATION = "enable-originator-creation-during-loan-application";
8383
public static final String PASSWORD_REUSE_CHECK_HISTORY_COUNT = "password-reuse-check-history-count";
84+
public static final String FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT = "allow-force-withdrawal-on-savings-account";
85+
public static final String FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT_LIMIT = "force-withdrawal-on-savings-account-limit";
8486
public static final String FORCE_PASSWORD_RESET_ON_FIRST_LOGIN = "force-password-reset-on-first-login";
8587

8688
private GlobalConfigurationConstants() {}

fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ public interface ConfigurationDomainService {
152152

153153
String getAssetOwnerTransferOustandingInterestStrategy();
154154

155+
boolean isForceWithdrawalOnSavingsAccountEnabled();
156+
157+
Long retrieveForceWithdrawalOnSavingsAccountLimit();
158+
155159
Integer getPasswordReuseRestrictionCount();
156160

157161
boolean isForcePasswordResetOnFirstLoginEnabled();

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ private List<String> validateRepaymentScheduleTotal(List<String> header, LoanSch
366366
PostLoansLoanIdTransactionsRequest setReAgeingRequestProperties(PostLoansLoanIdTransactionsRequest request, List<String> headers,
367367
List<String> values) {
368368
for (int i = 0; i < headers.size(); i++) {
369-
String header = headers.get(i).toLowerCase().trim().replaceAll(" ", "");
369+
String header = headers.get(i).toLowerCase(java.util.Locale.ROOT).trim().replaceAll(" ", "");
370370
switch (header) {
371371
case "frequencynumber" -> request.setFrequencyNumber(Integer.parseInt(values.get(i)));
372372
case "frequencytype" -> request.setFrequencyType(values.get(i));

fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1312,7 +1312,7 @@ public void initialize() throws Exception {
13121312
.recalculationRestFrequencyType(1)//
13131313
.recalculationRestFrequencyInterval(1)//
13141314
.repaymentEvery(1)//
1315-
.interestRatePerPeriod((double) 7.0)//
1315+
.interestRatePerPeriod(7.0)//
13161316
.interestRateFrequencyType(INTEREST_RATE_FREQUENCY_TYPE_MONTH)//
13171317
.enableDownPayment(false)//
13181318
.interestRecalculationCompoundingMethod(0)//

fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,16 @@ public String getAssetOwnerTransferOustandingInterestStrategy() {
549549
GlobalConfigurationConstants.ASSET_OWNER_TRANSFER_OUTSTANDING_INTEREST_CALCULATION_STRATEGY).getStringValue();
550550
}
551551

552+
@Override
553+
public boolean isForceWithdrawalOnSavingsAccountEnabled() {
554+
return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT).isEnabled();
555+
}
556+
557+
@Override
558+
public Long retrieveForceWithdrawalOnSavingsAccountLimit() {
559+
return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT_LIMIT).getValue();
560+
}
561+
552562
@Override
553563
public Integer getPasswordReuseRestrictionCount() {
554564
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.infrastructure.event.business.domain.savings.transaction;
20+
21+
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
22+
23+
public class SavingsAccountForceWithdrawalBusinessEvent extends SavingsAccountTransactionBusinessEvent {
24+
25+
private static final String TYPE = "SavingsAccountForceWithdrawalBusinessEvent";
26+
27+
public SavingsAccountForceWithdrawalBusinessEvent(SavingsAccountTransaction value) {
28+
super(value);
29+
}
30+
31+
@Override
32+
public String getType() {
33+
return TYPE;
34+
}
35+
}

fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.apache.fineract.commands.domain.CommandWrapper;
4545
import org.apache.fineract.commands.service.CommandWrapperBuilder;
4646
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
47+
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
4748
import org.apache.fineract.infrastructure.core.api.JsonCommand;
4849
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
4950
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
@@ -134,6 +135,7 @@ public class InteropServiceImpl implements InteropService {
134135
private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
135136

136137
private final SavingsAccountDomainService savingsAccountService;
138+
private final ConfigurationDomainService configurationDomainService;
137139

138140
private final JdbcTemplate jdbcTemplate;
139141

@@ -566,7 +568,7 @@ private Loan validateAndGetLoan(String accountId) {
566568
private SavingsAccount validateAndGetSavingAccount(@NonNull InteropRequestData request) {
567569
// TODO: error handling
568570
SavingsAccount savingsAccount = validateAndGetSavingAccount(request.getAccountId());
569-
savingsAccount.setHelpers(savingsAccountTransactionSummaryWrapper, savingsHelper);
571+
savingsAccount.setHelpers(savingsAccountTransactionSummaryWrapper, savingsHelper, configurationDomainService);
570572

571573
ApplicationCurrency requestCurrency = currencyRepository.findOneByCode(request.getAmount().getCurrency());
572574
if (!savingsAccount.getCurrency().getCode().equals(requestCurrency.getCode())) {

fineract-provider/src/main/java/org/apache/fineract/interoperation/starter/InteroperationConfiguration.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.apache.fineract.interoperation.starter;
2020

2121
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
22+
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
2223
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
2324
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
2425
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -52,12 +53,12 @@ public InteropService interopService(PlatformSecurityContext securityContext, In
5253
PaymentTypeRepository paymentTypeRepository, InteropIdentifierRepository identifierRepository,
5354
LoanRepositoryWrapper loanRepositoryWrapper, SavingsHelper savingsHelper,
5455
SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
55-
SavingsAccountDomainService savingsAccountService, JdbcTemplate jdbcTemplate,
56-
PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
56+
SavingsAccountDomainService savingsAccountService, ConfigurationDomainService configurationDomainService,
57+
JdbcTemplate jdbcTemplate, PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
5758
DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer, DatabaseSpecificSQLGenerator sqlGenerator) {
5859
return new InteropServiceImpl(securityContext, interopDataValidator, savingsAccountRepository, savingsAccountTransactionRepository,
5960
applicationCurrencyRepository, noteRepository, paymentTypeRepository, identifierRepository, loanRepositoryWrapper,
60-
savingsHelper, savingsAccountTransactionSummaryWrapper, savingsAccountService, jdbcTemplate,
61+
savingsHelper, savingsAccountTransactionSummaryWrapper, savingsAccountService, configurationDomainService, jdbcTemplate,
6162
commandsSourceWritePlatformService, toApiJsonSerializer, sqlGenerator);
6263
}
6364
}

fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ public String transaction(@PathParam("savingsId") final Long savingsId, @QueryPa
189189
} else if (is(commandParam, "withdrawal")) {
190190
final CommandWrapper commandRequest = builder.savingsAccountWithdrawal(savingsId).build();
191191
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
192+
} else if (is(commandParam, "force-withdrawal")) {
193+
final CommandWrapper commandRequest = builder.savingsAccountForceWithdrawal(savingsId).build();
194+
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
192195
} else if (is(commandParam, "postInterestAsOn")) {
193196
final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(savingsId).build();
194197
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
@@ -200,7 +203,7 @@ public String transaction(@PathParam("savingsId") final Long savingsId, @QueryPa
200203
if (result == null) {
201204
//
202205
throw new UnrecognizedQueryParamException("command", commandParam,
203-
new Object[] { "deposit", "withdrawal", SavingsApiConstants.COMMAND_HOLD_AMOUNT });
206+
new Object[] { "deposit", "withdrawal", "force-withdrawal", SavingsApiConstants.COMMAND_HOLD_AMOUNT });
204207
}
205208

206209
return this.toApiJsonSerializer.serialize(result);

0 commit comments

Comments
 (0)