Skip to content

Commit d01fedf

Browse files
authored
Merge pull request #5349
FINERACT-2399: Add global config to block transactions on closed/overpaid loans
2 parents 60acf85 + 86ad89f commit d01fedf

12 files changed

Lines changed: 282 additions & 5 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public final class GlobalConfigurationConstants {
8787
public static final String FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT_LIMIT = "force-withdrawal-on-savings-account-limit";
8888
public static final String FORCE_PASSWORD_RESET_ON_FIRST_LOGIN = "force-password-reset-on-first-login";
8989
public static final String ALLOW_CASH_AND_NON_CASH_ACCRUAL = "allow-cash-and-non-cash-accrual";
90+
public static final String BLOCK_TRANSACTIONS_ON_CLOSED_OVERPAID_LOANS = "block-transactions-on-closed-overpaid-loans";
9091

9192
private GlobalConfigurationConstants() {}
9293
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,6 @@ public interface ConfigurationDomainService {
165165
Integer retrieveMaxLoginRetries();
166166

167167
boolean isAllowCashAndNonCashAccrual();
168+
169+
boolean isBlockTransactionsOnClosedOverpaidLoansEnabled();
168170
}

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public interface LoanTransactionValidator {
4040

4141
void validateTransaction(String json);
4242

43+
default void validateTransaction(Loan loan, LoanTransactionType loanTransactionType, String json) {
44+
validateTransaction(json);
45+
validateLoanNotClosedOrOverpaidForTransactions(loan, loanTransactionType);
46+
}
47+
4348
void validateChargebackTransaction(String json);
4449

4550
void validateNewRepaymentTransaction(String json);
@@ -98,4 +103,8 @@ void validateRefund(Loan loan, LoanTransactionType loanTransactionType, LocalDat
98103
void validateManualInterestRefundTransaction(String json);
99104

100105
void validateClassificationCodeValue(String codeName, Long transactionClassificationId, DataValidatorBuilder baseDataValidator);
106+
107+
void validateLoanNotClosedOrOverpaidForTransactions(Loan loan);
108+
109+
void validateLoanNotClosedOrOverpaidForTransactions(Loan loan, LoanTransactionType loanTransactionType);
101110
}

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,16 @@ public void validateLoanGroupIsActive(Loan loan) {
515515
loanTransactionValidator.validateLoanGroupIsActive(loan);
516516
}
517517

518+
@Override
519+
public void validateLoanNotClosedOrOverpaidForTransactions(Loan loan) {
520+
loanTransactionValidator.validateLoanNotClosedOrOverpaidForTransactions(loan);
521+
}
522+
523+
@Override
524+
public void validateLoanNotClosedOrOverpaidForTransactions(Loan loan, LoanTransactionType loanTransactionType) {
525+
loanTransactionValidator.validateLoanNotClosedOrOverpaidForTransactions(loan, loanTransactionType);
526+
}
527+
518528
@Override
519529
public void validateActivityNotBeforeLastTransactionDate(Loan loan, LocalDate activityDate, LoanEvent event) {
520530
loanTransactionValidator.validateActivityNotBeforeLastTransactionDate(loan, activityDate, event);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,4 +591,9 @@ public Integer retrieveMaxLoginRetries() {
591591
public boolean isAllowCashAndNonCashAccrual() {
592592
return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.ALLOW_CASH_AND_NON_CASH_ACCRUAL).isEnabled();
593593
}
594+
595+
@Override
596+
public boolean isBlockTransactionsOnClosedOverpaidLoansEnabled() {
597+
return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.BLOCK_TRANSACTIONS_ON_CLOSED_OVERPAID_LOANS).isEnabled();
598+
}
594599
}

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ public LoanTransaction makeRepayment(final LoanTransactionType repaymentTransact
218218
final boolean isRecoveryRepayment, final String chargeRefundChargeType, boolean isAccountTransfer,
219219
HolidayDetailDTO holidayDetailDto, Boolean isHolidayValidationDone, final boolean isLoanToLoanTransfer) {
220220
checkClientOrGroupActive(loan);
221+
loanTransactionValidator.validateLoanNotClosedOrOverpaidForTransactions(loan, repaymentTransactionType);
221222

222223
LoanBusinessEvent repaymentEvent = getLoanRepaymentTypeBusinessEvent(repaymentTransactionType, isRecoveryRepayment, loan);
223224
businessEventNotifierService.notifyPreBusinessEvent(repaymentEvent);

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.apache.commons.lang3.StringUtils;
3838
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
3939
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
40+
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
4041
import org.apache.fineract.infrastructure.core.api.JsonCommand;
4142
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
4243
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
@@ -111,6 +112,7 @@ public class LoanTransactionValidatorImpl implements LoanTransactionValidator {
111112
private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator;
112113
private final LoanDisbursementValidator loanDisbursementValidator;
113114
private final CodeValueRepository codeValueRepository;
115+
private final ConfigurationDomainService configurationDomainService;
114116

115117
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
116118
if (!dataValidationErrors.isEmpty()) {
@@ -667,6 +669,23 @@ public void validateLoanGroupIsActive(final Loan loan) {
667669
}
668670
}
669671

672+
@Override
673+
public void validateLoanNotClosedOrOverpaidForTransactions(Loan loan) {
674+
validateLoanNotClosedOrOverpaidForTransactions(loan, null);
675+
}
676+
677+
@Override
678+
public void validateLoanNotClosedOrOverpaidForTransactions(Loan loan, LoanTransactionType loanTransactionType) {
679+
boolean blockTransactions = configurationDomainService.isBlockTransactionsOnClosedOverpaidLoansEnabled();
680+
if (LoanTransactionType.CREDIT_BALANCE_REFUND.equals(loanTransactionType)) {
681+
return;
682+
}
683+
if (blockTransactions && (loan.isClosed() || loan.getStatus().isOverpaid())) {
684+
throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.not.allowed.on.closed.or.overpaid",
685+
"Monetary transactions are not allowed on closed or overpaid loan accounts", loan.getId());
686+
}
687+
}
688+
670689
protected void validateLoanHasNoLaterChargeRefundTransactionToReverseOrCreateATransaction(Loan loan, LocalDate transactionDate,
671690
String reversedOrCreated) {
672691
for (LoanTransaction txn : loan.getLoanTransactions()) {
@@ -789,6 +808,7 @@ public void validateLoanTransactionInterestPaymentWaiver(JsonCommand command) {
789808
validateLoanClientIsActive(loan);
790809
validateLoanHasCurrency(loan);
791810
validateLoanGroupIsActive(loan);
811+
validateLoanNotClosedOrOverpaidForTransactions(loan, LoanTransactionType.INTEREST_PAYMENT_WAIVER);
792812
loanDownPaymentTransactionValidator.validateLoanStatusIsActiveOrFullyPaidOrOverpaid(loan);
793813
validateLoanDisbursementIsBeforeTransactionDate(loan, transactionDate);
794814
validateLoanHasNoLaterChargeRefundTransactionToReverseOrCreateATransaction(loan, transactionDate, "created");
@@ -826,6 +846,7 @@ public void validateRefund(String json) {
826846
public void validateRefund(final Loan loan, LoanTransactionType loanTransactionType, final LocalDate transactionDate,
827847
ScheduleGeneratorDTO scheduleGeneratorDTO) {
828848
checkClientOrGroupActive(loan);
849+
validateLoanNotClosedOrOverpaidForTransactions(loan, loanTransactionType);
829850
loanDownPaymentTransactionValidator.validateLoanStatusIsActiveOrFullyPaidOrOverpaid(loan);
830851
validateActivityNotBeforeClientOrGroupTransferDate(loan, transactionDate);
831852
validateRepaymentTypeTransactionNotBeforeAChargeRefund(loan, loanTransactionType, transactionDate);

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ private Loan saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
699699
final Throwable realCause = e.getCause();
700700
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
701701
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
702-
if (realCause.getMessage().toLowerCase(java.util.Locale.ROOT).contains("external_id_unique")) {
702+
if (realCause.getMessage().toLowerCase(Locale.ROOT).contains("external_id_unique")) {
703703
baseDataValidator.reset().parameter(LoanApiConstants.externalIdParameterName).failWithCode("value.must.be.unique");
704704
}
705705
if (!dataValidationErrors.isEmpty()) {
@@ -717,7 +717,7 @@ private void saveLoanWithDataIntegrityViolationChecks(final Loan loan) {
717717
final Throwable realCause = e.getCause();
718718
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
719719
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
720-
if (realCause.getMessage().toLowerCase(java.util.Locale.ROOT).contains("external_id_unique")) {
720+
if (realCause.getMessage().toLowerCase(Locale.ROOT).contains("external_id_unique")) {
721721
baseDataValidator.reset().parameter(LoanApiConstants.externalIdParameterName).failWithCode("value.must.be.unique");
722722
}
723723
if (!dataValidationErrors.isEmpty()) {
@@ -1392,9 +1392,6 @@ private void validateLoanTransactionAmountChargeBack(LoanTransaction loanTransac
13921392
@Transactional
13931393
@Override
13941394
public CommandProcessingResult waiveInterestOnLoan(final Long loanId, final JsonCommand command) {
1395-
1396-
this.loanTransactionValidator.validateTransaction(command.json());
1397-
13981395
final Map<String, Object> changes = new LinkedHashMap<>();
13991396
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
14001397
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
@@ -1405,6 +1402,7 @@ public CommandProcessingResult waiveInterestOnLoan(final Long loanId, final Json
14051402
final ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
14061403

14071404
Loan loan = this.loanAssembler.assembleFrom(loanId);
1405+
loanTransactionValidator.validateTransaction(loan, LoanTransactionType.WAIVE_INTEREST, command.json());
14081406
checkClientOrGroupActive(loan);
14091407

14101408
final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount);

fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,5 @@
246246
<include file="parts/0225_add_originator_external_ids_to_aggregation_summary.xml" relativeToChangelogFile="true" />
247247
<include file="parts/0226_trial_balance_summary_fix_originator_join_conditions.xml" relativeToChangelogFile="true" />
248248
<include file="parts/0227_postgresql_client_and_loan_trends_reports.xml" relativeToChangelogFile="true" />
249+
<include file="parts/0228_add_configuration_block_transactions_on_closed_overpaid_loans.xml" relativeToChangelogFile="true" />
249250
</databaseChangeLog>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Licensed to the Apache Software Foundation (ASF) under one
5+
or more contributor license agreements. See the NOTICE file
6+
distributed with this work for additional information
7+
regarding copyright ownership. The ASF licenses this file
8+
to you under the Apache License, Version 2.0 (the
9+
"License"); you may not use this file except in compliance
10+
with the License. You may obtain a copy of the License at
11+
12+
http://www.apache.org/licenses/LICENSE-2.0
13+
14+
Unless required by applicable law or agreed to in writing,
15+
software distributed under the License is distributed on an
16+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
KIND, either express or implied. See the License for the
18+
specific language governing permissions and limitations
19+
under the License.
20+
21+
-->
22+
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
23+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
24+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
25+
26+
<changeSet author="fineract" id="2">
27+
<insert tableName="c_configuration">
28+
<column name="name" value="block-transactions-on-closed-overpaid-loans"/>
29+
<column name="value"/>
30+
<column name="date_value"/>
31+
<column name="string_value"/>
32+
<column name="enabled" valueBoolean="false"/>
33+
<column name="is_trap_door" valueBoolean="false"/>
34+
<column name="description" value="If enabled: monetary transactions are blocked on closed and overpaid loan accounts"/>
35+
</insert>
36+
</changeSet>
37+
</databaseChangeLog>

0 commit comments

Comments
 (0)