FINERACT-2326: Not implemented exception handling

This commit is contained in:
Adam Saghy
2025-12-03 12:40:55 +01:00
parent c50ed19959
commit f55b63432e
5 changed files with 120 additions and 9 deletions
@@ -25,6 +25,7 @@ import static org.apache.http.HttpStatus.SC_FORBIDDEN;
import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
import static org.apache.http.HttpStatus.SC_METHOD_NOT_ALLOWED;
import static org.apache.http.HttpStatus.SC_NOT_FOUND;
import static org.apache.http.HttpStatus.SC_NOT_IMPLEMENTED;
import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
@@ -139,6 +140,15 @@ public class ApiGlobalErrorResponse {
"Errors contain reason for domain rule violation.", errors);
}
public static ApiGlobalErrorResponse notImplemented(final String globalisationMessageCode, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
final List<ApiParameterError> errors = new ArrayList<>();
errors.add(ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs));
return create(SC_NOT_IMPLEMENTED, "validation.msg.not.implemented", "Request was understood but not implemented.",
"Errors contain reason for not implemented violation.", errors);
}
public static ApiGlobalErrorResponse dataIntegrityError(final String globalisationMessageCode, final String defaultUserMessage,
final String parameterName, final Object... defaultUserMessageArgs) {
final List<ApiParameterError> errors = new ArrayList<>();
@@ -0,0 +1,56 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.exceptionmapper;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* An {@link ExceptionMapper} to map {@link UnsupportedOperationException} thrown by platform into a HTTP API friendly
* format.
*
* The {@link UnsupportedOperationException} is thrown when an operation was not yet implemented.
*/
@Provider
@Component
@Scope("singleton")
@Slf4j
public class UnsupporterOperationExceptionMapper implements FineractExceptionMapper, ExceptionMapper<UnsupportedOperationException> {
@Override
public Response toResponse(final UnsupportedOperationException exception) {
log.warn("Exception occurred", ErrorHandler.findMostSpecificException(exception));
final ApiGlobalErrorResponse notFoundErrorResponse = ApiGlobalErrorResponse.notImplemented("not.implemented",
exception.getMessage());
return Response.status(Status.NOT_IMPLEMENTED).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
}
@Override
public int errorCode() {
return 5010;
}
}
@@ -538,4 +538,21 @@ public class LoanReAgingStepDef extends AbstractStepDef {
}
return request;
}
@When("Admin creates a Loan re-aging transaction by Loan external ID with the following data, but fails with {int} error code:")
public void adminCreatesALoanReAgingTransactionByLoanExternalIDWithTheFollowingDataButFailsWithErrorCode(int errorCode,
DataTable table) {
PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
String loanExternalId = loanResponse.getResourceExternalId();
PostLoansLoanIdTransactionsRequest reAgingRequest = setReAgeingRequestProperties(//
LoanRequestFactory.defaultReAgingRequest(), //
table.row(0), //
table.row(1) //
);
CallFailedRuntimeException response = fail(() -> fineractClient.loanTransactions().executeLoanTransaction1(loanExternalId,
reAgingRequest, Map.<String, Object>of("command", "reAge")));
assertThat(response.getStatus()).isEqualTo(errorCode);
}
}
@@ -12320,7 +12320,6 @@ Feature: LoanReAging
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false |
| 01 March 2024 | Repayment | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 67.05 | true | false |
| 15 March 2024 | Re-age | 85.08 | 83.57 | 1.51 | 0.0 | 0.0 | 0.0 | false | true |
When Loan Pay-off is made on "01 April 2024"
Then Loan is closed with zero outstanding balance and it's all installments have obligations met
@@ -14537,4 +14536,34 @@ Feature: LoanReAging
When Loan Pay-off is made on "01 April 2024"
Then Loan is closed with zero outstanding balance and it's all installments have obligations met
@TestRailId:C4272 @AdvancedPaymentAllocation
Scenario: Verify Re-aging on interest bearing, no interest recalculation loan is not allowed (not implemented)
When Admin sets the business date to "01 January 2024"
When Admin creates a client with random data
When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_CHARGEBACK_PRINCIPAL_INTEREST_FEE" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
When Admin creates a fully customized loan with the following data:
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
| LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_CHARGEBACK_PRINCIPAL_INTEREST_FEE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024"
When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024| | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
When Admin creates a Loan re-aging transaction by Loan external ID with the following data, but fails with 501 error code:
| frequencyNumber | frequencyType | startDate | numberOfInstallments |
| 1 | MONTHS | 16 January 2025 | 5 |
When Loan Pay-off is made on "01 January 2024"
Then Loan is closed with zero outstanding balance and it's all installments have obligations met
@@ -62,7 +62,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@@ -165,34 +164,34 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
protected Money handleTransactionThatIsALateRepaymentOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
List<LoanRepaymentScheduleInstallment> installments, LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
throw new NotImplementedException();
throw new UnsupportedOperationException();
}
@Override
protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
List<LoanRepaymentScheduleInstallment> installments, LoanTransaction loanTransaction, Money paymentInAdvance,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
throw new NotImplementedException();
throw new UnsupportedOperationException();
}
@Override
protected Money handleTransactionThatIsOnTimePaymentOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges) {
throw new NotImplementedException();
throw new UnsupportedOperationException();
}
@Override
protected Money handleRefundTransactionPaymentOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
throw new NotImplementedException();
throw new UnsupportedOperationException();
}
@Override
public Money handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> loanCharges) {
throw new NotImplementedException();
throw new UnsupportedOperationException();
}
// only for progressive loans
@@ -2957,12 +2956,12 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
handleReAgeWithInterestRecalculationEnabled(loanTransaction, progressiveTransactionCtx);
} else if (loanTransaction.getLoan().isInterestBearing() && !loanTransaction.getLoan().isInterestRecalculationEnabled()) {
// TODO: implement interestRecalculation = false logic
throw new NotImplementedException(
throw new UnsupportedOperationException(
"Logic for re-aging when interest bearing loan has interestRecalculation disabled is not implemented");
}
} else if (LoanReAgeInterestHandlingType.WAIVE_INTEREST.equals(loanReAgeParameter.getInterestHandlingType())) {
throw new NotImplementedException("WAIVE_INTEREST interest handling strategy for re-aging is not implemented");
throw new UnsupportedOperationException("WAIVE_INTEREST interest handling strategy for re-aging is not implemented");
} else {
if (LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST.equals(loanReAgeParameter.getInterestHandlingType())) {
CommonReAgeSettings settings = new CommonReAgeSettings(false, true, true, true);