diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferRequestParameters.java b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferRequestParameters.java index 127b58727..dca3343cd 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferRequestParameters.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferRequestParameters.java @@ -26,6 +26,6 @@ public final class ExternalTransferRequestParameters { public static final String OWNER_EXTERNAL_ID = "owner_external_id"; public static final String TRANSFER_EXTERNAL_ID = "transfer_external_id"; public static final String PURCHASE_PRICE_RATIO = "purchase_price_ratio"; - public static final String DATEFORMAT = "dateformat"; + public static final String DATEFORMAT = "dateFormat"; public static final String LOCALE = "locale"; } diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java index 50876c654..ad54875d1 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java @@ -22,17 +22,22 @@ import com.google.gson.JsonElement; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; import java.time.LocalDate; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.data.LoanIdAndExternalIdData; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; @@ -64,9 +69,10 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW public CommandProcessingResult saleLoanByLoanId(JsonCommand command) { Long loanId = command.getLoanId(); LoanIdAndExternalIdData loanIdAndExternalId = loanReadPlatformService.getTransferableLoanIdAndExternalId(loanId); + validateLoanStatus(loanIdAndExternalId); ExternalAssetOwnerTransfer externalAssetOwnerTransfer = parseJson(loanId, command.json(), loanIdAndExternalId.getLoanExternalId(), ExternalTransferStatus.PENDING); - validateSale(externalAssetOwnerTransfer, loanIdAndExternalId); + validateSale(externalAssetOwnerTransfer); ExternalAssetOwnerTransfer savedExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository.save(externalAssetOwnerTransfer); return buildResponseData(savedExternalAssetOwnerTransfer); } @@ -76,8 +82,9 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW public CommandProcessingResult buyBackLoanByLoanId(JsonCommand command) { Long loanId = command.getLoanId(); LoanIdAndExternalIdData loanIdAndExternalId = loanReadPlatformService.getTransferableLoanIdAndExternalId(loanId); - ExternalAssetOwnerTransfer externalAssetOwnerTransfer = parseJson(command.getLoanId(), command.json(), - loanIdAndExternalId.getLoanExternalId(), ExternalTransferStatus.BUYBACK); + validateLoanStatus(loanIdAndExternalId); + ExternalAssetOwnerTransfer externalAssetOwnerTransfer = parseJson(loanId, command.json(), loanIdAndExternalId.getLoanExternalId(), + ExternalTransferStatus.BUYBACK); validateBuyBack(externalAssetOwnerTransfer); ExternalAssetOwnerTransfer savedExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository.save(externalAssetOwnerTransfer); return buildResponseData(savedExternalAssetOwnerTransfer); @@ -99,10 +106,9 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW .with(changes).build(); } - private void validateSale(ExternalAssetOwnerTransfer externalAssetOwnerTransfer, LoanIdAndExternalIdData loanIdAndExternalId) { + private void validateSale(ExternalAssetOwnerTransfer externalAssetOwnerTransfer) { validateSettlementDate(externalAssetOwnerTransfer); validateTransferStatusForSale(externalAssetOwnerTransfer); - validateLoanStatus(loanIdAndExternalId); } private void validateBuyBack(ExternalAssetOwnerTransfer externalAssetOwnerTransfer) { @@ -117,7 +123,8 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW } private void validateLoanStatus(LoanIdAndExternalIdData loanIdAndExternalIdAndExternalId) { - if (Objects.isNull(loanIdAndExternalIdAndExternalId)) { + if (Objects.isNull(loanIdAndExternalIdAndExternalId.getLoanId()) + && Objects.isNull(loanIdAndExternalIdAndExternalId.getLoanExternalId())) { throw new ExternalAssetOwnerInitiateTransferException("Loan is not in active status"); } } @@ -182,6 +189,25 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW ExternalTransferRequestParameters.DATEFORMAT, ExternalTransferRequestParameters.LOCALE)); final Type typeOfMap = new TypeToken>() {}.getType(); fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, apiRequestBodyAsJson, requestParameters); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loantransfer"); + final JsonElement json = fromApiJsonHelper.parse(apiRequestBodyAsJson); + + String ownerExternalId = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID, json); + baseDataValidator.reset().parameter(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID).value(ownerExternalId).notBlank() + .notExceedingLengthOf(100); + + String transferExternalId = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID, json); + baseDataValidator.reset().parameter(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID).value(transferExternalId).ignoreIfNull() + .notExceedingLengthOf(100); + + String purchasePriceRatio = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO, json); + baseDataValidator.reset().parameter(ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO).value(purchasePriceRatio).notBlank() + .notExceedingLengthOf(50); + + LocalDate settlementDate = fromApiJsonHelper.extractLocalDateNamed(ExternalTransferRequestParameters.SETTLEMENT_DATE, json); + baseDataValidator.reset().parameter(ExternalTransferRequestParameters.SETTLEMENT_DATE).value(settlementDate).notNull(); } private LocalDate getSettlementDateFromJson(JsonElement json) { @@ -193,7 +219,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW private ExternalId getTransferExternalIdFromJson(JsonElement json) { String transferExternalId = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID, json); - return ExternalIdFactory.produce(transferExternalId); + return StringUtils.isEmpty(transferExternalId) ? ExternalId.generate() : ExternalIdFactory.produce(transferExternalId); } private String getPurchasePriceRatioFromJson(JsonElement json) { @@ -210,6 +236,6 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW private ExternalAssetOwner createAndGetAssetOwner(String externalId) { ExternalAssetOwner externalAssetOwner = new ExternalAssetOwner(); externalAssetOwner.setExternalId(ExternalIdFactory.produce(externalId)); - return externalAssetOwnerRepository.save(externalAssetOwner); + return externalAssetOwnerRepository.saveAndFlush(externalAssetOwner); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java index 085cc59b7..3f7952d11 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.domain; import java.time.LocalDate; import java.util.Collection; import java.util.List; +import java.util.Optional; import org.apache.fineract.cob.data.LoanCOBParameter; import org.apache.fineract.cob.data.LoanIdAndExternalIdAndAccountNo; import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate; @@ -197,7 +198,7 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat Loan findLoanAccountByAccountNumber(@Param("accountNumber") String accountNumber); @Query(GET_NON_CLOSED_LOAN_BY_LOAN_ID) - Loan getNonClosedLoanIdAndExternalIdByLoanId(@Param("loanId") Long loanId); + Optional getNonClosedLoanIdAndExternalIdByLoanId(@Param("loanId") Long loanId); @Query(EXISTS_NON_CLOSED_BY_EXTERNAL_LOAN_ID) boolean existsNonClosedLoanByExternalLoanId(@Param("externalLoanId") ExternalId externalLoanId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java index f861c3869..f072a5073 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepositoryWrapper.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.domain.ExternalId; @@ -263,7 +264,10 @@ public class LoanRepositoryWrapper { return repository.findLoanIdByStatusId(statusId); } - public Loan getNonClosedLoanIdAndExternalIdByLoanId(Long loanId) { + public Optional getNonClosedLoanIdAndExternalIdByLoanId(Long loanId) { + if (repository.findById(loanId).isEmpty()) { + throw new LoanNotFoundException(loanId); + } return repository.getNonClosedLoanIdAndExternalIdByLoanId(loanId); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 60754d1b3..09549592c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -594,8 +594,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa @Override public LoanIdAndExternalIdData getTransferableLoanIdAndExternalId(Long loanId) { - Loan loan = loanRepositoryWrapper.getNonClosedLoanIdAndExternalIdByLoanId(loanId); - return new LoanIdAndExternalIdData(loan.getId(), loan.getExternalId()); + Optional loan = loanRepositoryWrapper.getNonClosedLoanIdAndExternalIdByLoanId(loanId); + return loan.map(value -> new LoanIdAndExternalIdData(value.getId(), value.getExternalId())) + .orElseGet(() -> new LoanIdAndExternalIdData(null, null)); } @Override diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java index fe78adeab..f76d727f0 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java @@ -124,6 +124,7 @@ public class InitiateExternalAssetOwnerTransferTest { requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); requestSpec.header("Fineract-Platform-TenantId", "default"); responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, todaysDate); GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); } } @@ -180,6 +181,62 @@ public class InitiateExternalAssetOwnerTransferTest { requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); requestSpec.header("Fineract-Platform-TenantId", "default"); responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, todaysDate); + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + } + } + + @Test + public void saleIsNotAllowedWhenLoanIsNotActive() { + try { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 2)); + GlobalConfigurationHelper.updateValueForGlobalConfiguration(requestSpec, responseSpec, "10", "0"); + + final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec); + Assertions.assertNotNull(clientID); + + Integer overdueFeeChargeId = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentage("1")); + Assertions.assertNotNull(overdueFeeChargeId); + + final Integer loanProductID = createLoanProduct(overdueFeeChargeId.toString()); + Assertions.assertNotNull(loanProductID); + HashMap loanStatusHashMap; + + final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, "10 January 2020"); + + Assertions.assertNotNull(loanID); + + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + loanStatusHashMap = loanTransactionHelper.approveLoan("01 March 2020", loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + + String loanDetails = loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID); + loanStatusHashMap = loanTransactionHelper.disburseLoanWithNetDisbursalAmount("02 March 2020", loanID, + JsonPath.from(loanDetails).get("netDisbursalAmount").toString()); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 3, 4)); + + loanTransactionHelper.makeRepayment("04 March 2020", 16000.0f, loanID); + + externalAssetOwnerHelper = new ExternalAssetOwnerHelper(requestSpec, responseSpecError); + String transferExternalId = "36efeb06-d835-48a1-99eb-09bd1d348c1e"; + String saleResponse = externalAssetOwnerHelper.initiateTransferByLoanId(loanID.longValue(), "sale", + getSaleRequestJson("05 March 2020", transferExternalId)); + Type type = new TypeToken>() {}.getType(); + Map errorResponseMap = new Gson().fromJson(saleResponse, type); + assertEquals("Loan is not in active status", errorResponseMap.get("developerMessage")); + } finally { + requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + requestSpec.header("Fineract-Platform-TenantId", "default"); + responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, todaysDate); GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); } } @@ -244,6 +301,7 @@ public class InitiateExternalAssetOwnerTransferTest { requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); requestSpec.header("Fineract-Platform-TenantId", "default"); responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, todaysDate); GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); } } @@ -292,7 +350,7 @@ public class InitiateExternalAssetOwnerTransferTest { map.put("owner_external_id", "1234567890987654321"); map.put("transfer_external_id", transferExternalId); map.put("purchase_price_ratio", "1.234"); - map.put("dateformat", "dd MMMM yyyy"); + map.put("dateFormat", "dd MMMM yyyy"); map.put("locale", "en"); return new Gson().toJson(map); }