diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java index 582c66d69..7ea034c07 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.charge.exception; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; @@ -38,4 +40,9 @@ public class LoanChargeNotFoundException extends AbstractPlatformResourceNotFoun super("error.msg.loanCharge.id.invalid.for.given.loan", "Loan charge with identifier " + id + " does not exist for loan " + loanId, id, loanId); } + + public LoanChargeNotFoundException(ExternalId externalId) { + super("error.msg.loanCharge.external.id.invalid", "Loan Charge with external identifier " + + ObjectUtils.defaultIfNull(externalId, ExternalId.empty()).getValue() + " does not exist", externalId); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java index cc5bdcd04..2778b3302 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java @@ -58,9 +58,11 @@ import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; +import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundException; import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData; +import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.springframework.context.annotation.Scope; @@ -549,6 +551,9 @@ public class LoanChargesApiResource { if (resolvedLoanChargeId == null) { loanChargeExternalId.throwExceptionIfEmpty(); resolvedLoanChargeId = this.loanChargeReadPlatformService.retrieveLoanChargeIdByExternalId(loanChargeExternalId); + if (resolvedLoanChargeId == null) { + throw new LoanChargeNotFoundException(loanChargeExternalId); + } } return resolvedLoanChargeId; } @@ -558,6 +563,9 @@ public class LoanChargesApiResource { if (resolvedLoanId == null) { loanExternalId.throwExceptionIfEmpty(); resolvedLoanId = this.loanReadPlatformService.retrieveLoanIdByExternalId(loanExternalId.getValue()); + if (resolvedLoanId == null) { + throw new LoanNotFoundException(loanExternalId); + } } return resolvedLoanId; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java index 3d65f5697..7c3da1a92 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java @@ -62,6 +62,8 @@ import org.apache.fineract.infrastructure.security.service.PlatformSecurityConte import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; +import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; +import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException; import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; @@ -576,6 +578,9 @@ public class LoanTransactionsApiResource { if (resolvedLoanTransactionId == null) { externalTransactionId.throwExceptionIfEmpty(); resolvedLoanTransactionId = this.loanReadPlatformService.retrieveLoanTransactionIdByExternalId(externalTransactionId); + if (resolvedLoanTransactionId == null) { + throw new LoanTransactionNotFoundException(externalTransactionId); + } } return resolvedLoanTransactionId; } @@ -585,6 +590,9 @@ public class LoanTransactionsApiResource { if (resolvedLoanId == null) { loanExternalId.throwExceptionIfEmpty(); resolvedLoanId = this.loanReadPlatformService.retrieveLoanIdByExternalId(loanExternalId.getValue()); + if (resolvedLoanId == null) { + throw new LoanNotFoundException(loanExternalId); + } } return resolvedLoanId; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanNotFoundException.java index 9e8d17a43..73bb598a8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanNotFoundException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanNotFoundException.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.exception; +import org.apache.commons.lang3.ObjectUtils; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; @@ -39,10 +40,14 @@ public class LoanNotFoundException extends AbstractPlatformResourceNotFoundExcep } public LoanNotFoundException(ExternalId externalId) { - super("error.msg.loan.external.id.invalid", "Loan with external identifier " + externalId + " does not exist", externalId); + super("error.msg.loan.external.id.invalid", + "Loan with external identifier " + ObjectUtils.defaultIfNull(externalId, ExternalId.empty()).getValue() + " does not exist", + externalId); } public LoanNotFoundException(ExternalId externalId, Exception e) { - super("error.msg.loan.external.id.invalid", "Loan with external identifier " + externalId + " does not exist", externalId, e); + super("error.msg.loan.external.id.invalid", + "Loan with external identifier " + ObjectUtils.defaultIfNull(externalId, ExternalId.empty()).getValue() + " does not exist", + externalId, e); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTransactionNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTransactionNotFoundException.java index ee31742de..05ab128e0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTransactionNotFoundException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/LoanTransactionNotFoundException.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.exception; +import org.apache.commons.lang3.ObjectUtils; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; import org.springframework.dao.EmptyResultDataAccessException; @@ -40,8 +41,15 @@ public class LoanTransactionNotFoundException extends AbstractPlatformResourceNo super("error.msg.loan.transaction.id.invalid", "Transaction with identifier " + id + " does not exist", id, e); } + public LoanTransactionNotFoundException(ExternalId transactionExternalId) { + super("error.msg.loan.transaction.external.id.invalid", "Transaction with external identifier " + + ObjectUtils.defaultIfNull(transactionExternalId, ExternalId.empty()).getValue() + " does not exist", + transactionExternalId); + } + public LoanTransactionNotFoundException(ExternalId transactionExternalId, EmptyResultDataAccessException e) { - super("error.msg.loan.transaction.id.invalid", "Transaction with external identifier " + transactionExternalId + " does not exist", + super("error.msg.loan.transaction.external.id.invalid", "Transaction with external identifier " + + ObjectUtils.defaultIfNull(transactionExternalId, ExternalId.empty()).getValue() + " does not exist", transactionExternalId, e); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java index b98065e48..f5c7b25b6 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java @@ -20,6 +20,8 @@ package org.apache.fineract.integrationtests; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.ResponseSpecBuilder; @@ -48,6 +50,7 @@ import org.apache.fineract.client.models.PutChargeTransactionChangesRequest; import org.apache.fineract.client.models.PutChargeTransactionChangesResponse; import org.apache.fineract.client.models.PutLoansLoanIdChargesChargeIdRequest; import org.apache.fineract.client.models.PutLoansLoanIdChargesChargeIdResponse; +import org.apache.fineract.client.util.CallFailedRuntimeException; import org.apache.fineract.integrationtests.client.IntegrationTest; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; @@ -157,15 +160,15 @@ public class ExternalIdSupportIntegrationTest extends IntegrationTest { assertNotNull(penalty2Result.getResourceExternalId()); // Check whether we can fetch transaction templates with proper result http code (HTTP 200..300) - ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "repayment", null, null, null)); - ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "payoutRefund", null, null, null)); - ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "waiveinterest", null, null, null)); - ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "close-rescheduled", null, null, null)); - ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "disburse", null, null, null)); - ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "recoverypayment", null, null, null)); - ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "refundbycash", null, null, null)); - ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "foreclosure", null, null, null)); - ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "creditBalanceRefund", null, null, null)); + loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "repayment", null, null, null); + loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "payoutRefund", null, null, null); + loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "waiveinterest", null, null, null); + loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "close-rescheduled", null, null, null); + loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "disburse", null, null, null); + loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "recoverypayment", null, null, null); + loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "refundbycash", null, null, null); + loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "foreclosure", null, null, null); + loanTransactionHelper.retrieveTransactionTemplate(loanExternalIdStr, "creditBalanceRefund", null, null, null); // Check whether an external id was generated String waiveChargeExternalIdStr = UUID.randomUUID().toString(); @@ -667,6 +670,136 @@ public class ExternalIdSupportIntegrationTest extends IntegrationTest { GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, 50, false); } + @Test + public void negativeTest() { + // INIT + final Account assetAccount = this.accountHelper.createAssetAccount(); + final Account assetFeeAndPenaltyAccount = this.accountHelper.createAssetAccount(); + final Account incomeAccount = this.accountHelper.createIncomeAccount(); + final Account expenseAccount = this.accountHelper.createExpenseAccount(); + final Account overpaymentAccount = this.accountHelper.createLiabilityAccount(); + + final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth() + .withRepaymentAfterEvery("1").withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0") + .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat() + .withAccountingRulePeriodicAccrual(new Account[] { assetAccount, incomeAccount, expenseAccount, overpaymentAccount }) + .withDaysInMonth("30").withDaysInYear("365").withMoratorium("0", "0") + .withFeeAndPenaltyAssetAccount(assetFeeAndPenaltyAccount).build(null); + final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJSON); + + final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()); + + String loanExternalIdStr = UUID.randomUUID().toString(); + final HashMap loan = applyForLoanApplication(client.getClientId().intValue(), loanProductID, loanExternalIdStr); + Integer loanId = (Integer) loan.get("resourceId"); + + this.loanTransactionHelper.approveLoan("02 September 2022", loanId); + String txnExternalIdStr = UUID.randomUUID().toString(); + final HashMap disbursedLoanResult = this.loanTransactionHelper.disburseLoan("03 September 2022", loanId, "1000", txnExternalIdStr); + + // Check whether the provided external id was retrieved + assertEquals(txnExternalIdStr, disbursedLoanResult.get("resourceExternalId")); + + // NEGATIVE SCENARIOS + + // GET + CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, () -> this.loanTransactionHelper + .retrieveTransactionTemplate("randomNonExistingLoanExternalId", "disburse", null, null, null)); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, () -> this.loanTransactionHelper + .getLoanTransactionDetails("randomNonExistingLoanExternalId", "randomNonExistingLoanTransactionExternalId")); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, () -> this.loanTransactionHelper + .getLoanTransactionDetails(loanExternalIdStr, "randomNonExistingLoanTransactionExternalId")); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.transaction.external.id.invalid")); + + // POST + exception = assertThrows(CallFailedRuntimeException.class, () -> this.loanTransactionHelper + .makeChargeRefund("randomNonExistingLoanExternalId", new PostLoansLoanIdTransactionsRequest())); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, + () -> this.loanTransactionHelper.adjustLoanTransaction("randomNonExistingLoanExternalId", + "randomNonExistingLoanTransactionExternalId", new PostLoansLoanIdTransactionsTransactionIdRequest())); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, () -> this.loanTransactionHelper.adjustLoanTransaction(loanExternalIdStr, + "randomNonExistingLoanTransactionExternalId", new PostLoansLoanIdTransactionsTransactionIdRequest())); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.transaction.external.id.invalid")); + + // PUT + exception = assertThrows(CallFailedRuntimeException.class, + () -> this.loanTransactionHelper.undoWaiveLoanCharge("randomNonExistingLoanExternalId", + "randomNonExistingLoanTransactionExternalId", new PutChargeTransactionChangesRequest())); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, () -> this.loanTransactionHelper.undoWaiveLoanCharge(loanExternalIdStr, + "randomNonExistingLoanTransactionExternalId", new PutChargeTransactionChangesRequest())); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.transaction.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, + () -> this.loanTransactionHelper.getLoanCharges("randomNonExistingLoanExternalId")); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, + () -> this.loanTransactionHelper.getLoanChargeTemplate("randomNonExistingLoanExternalId")); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, + () -> this.loanTransactionHelper.getLoanCharge("randomNonExistingLoanExternalId", "randomNonExistingLoanChargeExternalId")); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, + () -> this.loanTransactionHelper.getLoanCharge(loanExternalIdStr, "randomNonExistingLoanChargeExternalId")); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loanCharge.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, + () -> this.loanTransactionHelper.payLoanCharge("randomNonExistingLoanExternalId", "randomNonExistingLoanChargeExternalId", + new PostLoansLoanIdChargesChargeIdRequest())); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, () -> this.loanTransactionHelper.payLoanCharge(loanExternalIdStr, + "randomNonExistingLoanChargeExternalId", new PostLoansLoanIdChargesChargeIdRequest())); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loanCharge.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, + () -> this.loanTransactionHelper.updateLoanCharge("randomNonExistingLoanExternalId", + "randomNonExistingLoanChargeExternalId", new PutLoansLoanIdChargesChargeIdRequest())); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, () -> this.loanTransactionHelper.updateLoanCharge(loanExternalIdStr, + "randomNonExistingLoanChargeExternalId", new PutLoansLoanIdChargesChargeIdRequest())); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loanCharge.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, () -> this.loanTransactionHelper + .deleteLoanCharge("randomNonExistingLoanExternalId", "randomNonExistingLoanChargeExternalId")); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loan.external.id.invalid")); + + exception = assertThrows(CallFailedRuntimeException.class, + () -> this.loanTransactionHelper.deleteLoanCharge(loanExternalIdStr, "randomNonExistingLoanChargeExternalId")); + assertEquals(404, exception.getResponse().code()); + assertTrue(exception.getMessage().contains("error.msg.loanCharge.external.id.invalid")); + } + private HashMap applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String externalId) { final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1") .withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1") diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java index 39edb4b97..1eba90212 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java @@ -52,6 +52,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod; import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule; import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdSummary; +import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse; import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse; import org.apache.fineract.client.models.GetPaymentTypesResponse; import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest; @@ -1612,4 +1613,10 @@ public class LoanTransactionHelper extends IntegrationTest { return chargebackPayload; } + public GetLoansLoanIdTransactionsTemplateResponse retrieveTransactionTemplate(String loanExternalIdStr, String command, + String dateFormat, String transactionDate, String locale) { + return ok( + fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, command, dateFormat, transactionDate, locale)); + } + }