From 5c25688639f8c854705396dc05e6301339436a89 Mon Sep 17 00:00:00 2001 From: Arnold Galovics Date: Thu, 9 Jan 2025 15:16:58 +0100 Subject: [PATCH] FINERACT-2081: Ability to assign settlement information to loan product when asset is externally owned --- build.gradle | 3 + .../fineract/client/util/FineractClient.java | 3 + .../service/CommandWrapperBuilder.java | 17 + .../fineract/test/api/ApiConfiguration.java | 6 + fineract-investor/build.gradle | 1 + fineract-investor/dependencies.gradle | 2 + ...wnerLoanProductAttributesApiConstants.java | 44 ++ ...OwnerLoanProductAttributesApiResource.java | 131 ++++++ ...anProductAttributesApiResourceSwagger.java | 52 +++ ...LoanProductAttributeRequestParameters.java | 27 ++ ...rnalTransferLoanProductAttributesData.java | 37 ++ ...xternalAssetOwnerLoanProductAttribute.java | 26 ++ ...xternalAssetOwnerLoanProductAttribute.java | 42 ++ ...ternalAssetOwnerLoanProductAttributes.java | 45 ++ ...tOwnerLoanProductAttributesRepository.java | 31 ++ ...roductAttributeAlreadyExistsException.java | 31 ++ ...teInvalidSettlementAttributeException.java | 31 ++ ...LoanProductAttributeNotFoundException.java | 31 ++ ...etOwnerLoanProductAttributesException.java | 30 ++ ...AssetOwnerLoanProductAttributeHandler.java | 39 ++ ...AssetOwnerLoanProductAttributesMapper.java | 32 ++ ...OwnerLoanProductAttributesReadService.java | 29 ++ ...rLoanProductAttributesReadServiceImpl.java | 86 ++++ ...wnerLoanProductAttributesWriteService.java | 30 ++ ...LoanProductAttributesWriteServiceImpl.java | 192 +++++++++ ...AssetOwnerLoanProductAttributeHandler.java | 39 ++ .../investor/module-changelog-master.xml | 1 + ...r_loan_product_configurable_attributes.xml | 112 +++++ ...rLoanProductAttributesReadServiceTest.java | 164 ++++++++ ...ProductAttributesWriteServiceImplTest.java | 389 ++++++++++++++++++ .../domain/LoanProductRepository.java | 5 + 31 files changed, 1708 insertions(+) create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiConstants.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiResource.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiResourceSwagger.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalAssetOwnerLoanProductAttributeRequestParameters.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferLoanProductAttributesData.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/data/attribute/ExternalAssetOwnerLoanProductAttribute.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/data/attribute/SettlementModelExternalAssetOwnerLoanProductAttribute.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerLoanProductAttributes.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerLoanProductAttributesRepository.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeAlreadyExistsException.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeNotFoundException.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributesException.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/service/CreateExternalAssetOwnerLoanProductAttributeHandler.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesMapper.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadService.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadServiceImpl.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteService.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteServiceImpl.java create mode 100644 fineract-investor/src/main/java/org/apache/fineract/investor/service/UpdateExternalAssetOwnerLoanProductAttributeHandler.java create mode 100644 fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0014_add_external_asset_owner_loan_product_configurable_attributes.xml create mode 100644 fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadServiceTest.java create mode 100644 fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteServiceImplTest.java diff --git a/build.gradle b/build.gradle index 9054adf6a05..ad23be81196 100644 --- a/build.gradle +++ b/build.gradle @@ -610,6 +610,9 @@ configure(project.fineractJavaProjects) { 'com.google.truth:truth', 'com.google.truth.extensions:truth-java8-extension' ) + + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' } test { diff --git a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java index a91363c1c26..f722c3488a3 100644 --- a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java +++ b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java @@ -65,6 +65,7 @@ import org.apache.fineract.client.services.DocumentsApiFixed; import org.apache.fineract.client.services.EntityDataTableApi; import org.apache.fineract.client.services.EntityFieldConfigurationApi; +import org.apache.fineract.client.services.ExternalAssetOwnerLoanProductAttributesApi; import org.apache.fineract.client.services.ExternalAssetOwnersApi; import org.apache.fineract.client.services.ExternalEventConfigurationApi; import org.apache.fineract.client.services.ExternalServicesApi; @@ -291,6 +292,7 @@ public final class FineractClient { public final WorkingDaysApi workingDays; public final ExternalAssetOwnersApi externalAssetOwners; + public final ExternalAssetOwnerLoanProductAttributesApi externalAssetOwnerLoanProductAttributes; public final LoanAccountLockApi loanAccountLockApi; private FineractClient(OkHttpClient okHttpClient, Retrofit retrofit) { @@ -299,6 +301,7 @@ private FineractClient(OkHttpClient okHttpClient, Retrofit retrofit) { loanAccountLockApi = retrofit.create(LoanAccountLockApi.class); externalAssetOwners = retrofit.create(ExternalAssetOwnersApi.class); + externalAssetOwnerLoanProductAttributes = retrofit.create(ExternalAssetOwnerLoanProductAttributesApi.class); glClosures = retrofit.create(AccountingClosureApi.class); accountingRules = retrofit.create(AccountingRulesApi.class); accountNumberFormats = retrofit.create(AccountNumberFormatApi.class); diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 740fbbbc67a..7e0ceca42ef 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -3644,6 +3644,23 @@ public CommandWrapperBuilder undoChargeOff(final Long loanId) { return this; } + public CommandWrapperBuilder createExternalAssetOwnerLoanProductAttribute(final Long loanProductId) { + this.actionName = "CREATE"; + this.entityName = "EXTERNAL_ASSET_OWNER_LOAN_PRODUCT_ATTRIBUTE"; + this.productId = loanProductId; + this.href = "/external-asset-owners/loan-product/" + loanProductId + "/attributes"; + return this; + } + + public CommandWrapperBuilder updateExternalAssetOwnerLoanProductAttribute(final Long loanProductId, final Long attributeId) { + this.actionName = "UPDATE"; + this.entityName = "EXTERNAL_ASSET_OWNER_LOAN_PRODUCT_ATTRIBUTE"; + this.productId = loanProductId; + this.entityId = attributeId; + this.href = "/external-asset-owners/loan-product/" + loanProductId + "/attributes/" + attributeId; + return this; + } + public CommandWrapperBuilder saleLoanToExternalAssetOwner(final Long loanId) { this.actionName = "SALE"; this.entityName = "LOAN"; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java index 768b9166c30..0607b76b9d7 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java @@ -29,6 +29,7 @@ import org.apache.fineract.client.services.DataTablesApi; import org.apache.fineract.client.services.DefaultApi; import org.apache.fineract.client.services.DelinquencyRangeAndBucketsManagementApi; +import org.apache.fineract.client.services.ExternalAssetOwnerLoanProductAttributesApi; import org.apache.fineract.client.services.ExternalAssetOwnersApi; import org.apache.fineract.client.services.ExternalEventConfigurationApi; import org.apache.fineract.client.services.FundsApi; @@ -214,6 +215,11 @@ public ExternalAssetOwnersApi externalAssetOwnersApi() { return fineractClient.createService(ExternalAssetOwnersApi.class); } + @Bean + public ExternalAssetOwnerLoanProductAttributesApi externalAssetOwnerLoanProductAttributesApi() { + return fineractClient.createService(ExternalAssetOwnerLoanProductAttributesApi.class); + } + @Bean public BusinessStepConfigurationApi businessStepConfigurationApi() { return fineractClient.createService(BusinessStepConfigurationApi.class); diff --git a/fineract-investor/build.gradle b/fineract-investor/build.gradle index 22187772f06..d73d6e1579a 100644 --- a/fineract-investor/build.gradle +++ b/fineract-investor/build.gradle @@ -20,6 +20,7 @@ description = 'Fineract Investor' apply plugin: 'java' apply plugin: 'eclipse' +apply plugin: 'org.springframework.boot' compileJava.doLast { def mainSS = sourceSets.main diff --git a/fineract-investor/dependencies.gradle b/fineract-investor/dependencies.gradle index c39bee8640e..02c5931641a 100644 --- a/fineract-investor/dependencies.gradle +++ b/fineract-investor/dependencies.gradle @@ -36,6 +36,7 @@ dependencies { implementation( 'org.springframework.boot:spring-boot-starter-web', 'org.springframework.boot:spring-boot-starter-security', + 'org.springframework.boot:spring-boot-starter-cache', 'jakarta.ws.rs:jakarta.ws.rs-api', 'org.glassfish.jersey.media:jersey-media-multipart', @@ -55,6 +56,7 @@ dependencies { 'org.mapstruct:mapstruct', 'io.github.resilience4j:resilience4j-spring-boot3', + 'org.reflections:reflections:0.10.2', ) compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiConstants.java b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiConstants.java new file mode 100644 index 00000000000..1a2c8837e5f --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiConstants.java @@ -0,0 +1,44 @@ +/** + * 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.investor.api; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributes; + +public final class ExternalAssetOwnerLoanProductAttributesApiConstants { + + private ExternalAssetOwnerLoanProductAttributesApiConstants() { + + } + + // parameters + private static final String loanProductIdParamName = "loanProductId"; + private static final String attributeKeyParamName = "attributeKey"; + private static final String attributeValueParamName = "attributeValue"; + private static final String id = "id"; + + /** + * These parameters will match the class level parameters of {@link ExternalAssetOwnerLoanProductAttributes}. Where + * possible, we try to get response parameters to match those of request parameters. + */ + static final Set RESPONSE_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(id, loanProductIdParamName, attributeKeyParamName, attributeValueParamName)); +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiResource.java b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiResource.java new file mode 100644 index 00000000000..8eafda5a0f3 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiResource.java @@ -0,0 +1,131 @@ +/** + * 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.investor.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.UriInfo; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.infrastructure.security.service.PlatformUserRightsContext; +import org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition; +import org.apache.fineract.investor.data.ExternalTransferLoanProductAttributesData; +import org.apache.fineract.investor.service.ExternalAssetOwnerLoanProductAttributesReadService; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; + +@Path("/v1/external-asset-owners/loan-product") +@Component +@Tag(name = "External Asset Owner Loan Product Attributes", description = "External Asset Owner Loan Product Attributes") +@RequiredArgsConstructor +@Conditional(InvestorModuleIsEnabledCondition.class) +public class ExternalAssetOwnerLoanProductAttributesApiResource { + + private final PlatformUserRightsContext platformUserRightsContext; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final ExternalAssetOwnerLoanProductAttributesReadService externalAssetOwnerLoanProductAttributesReadService; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + + @POST + @Path("/{loanProductId}/attributes") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = ExternalAssetOwnerLoanProductAttributesApiResourceSwagger.PostExternalAssetOwnerLoanProductAttributeRequest.class))) + @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Invalid Request"), + @ApiResponse(responseCode = "403", description = "Resource Already Exists"), + @ApiResponse(responseCode = "404", description = "Not Found"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") }) + public CommandProcessingResult postExternalAssetOwnerLoanProductAttribute( + @PathParam("loanProductId") @Parameter(description = "loanProductId") final Long loanProductId, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + platformUserRightsContext.isAuthenticated(); + final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); + CommandWrapper request = builder.createExternalAssetOwnerLoanProductAttribute(loanProductId).build(); + + return commandsSourceWritePlatformService.logCommandSource(request); + } + + @GET + @Path("/{loanProductId}/attributes") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(tags = { + "External Asset Owner Loan Product Attributes" }, summary = "Retrieve All Loan Product Attributes", description = "Retrieves all Loan Product Attributes with a given loanProductId", parameters = { + @Parameter(name = "loanProductId", description = "loanProductId"), + @Parameter(name = "attributeKey", description = "attributeKey") }) + @ApiResponses({ @ApiResponse(responseCode = "200", description = "A paginated group of loan product attributes is returned"), + @ApiResponse(responseCode = "404", description = "Not Found"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") }) + public Page getExternalAssetOwnerLoanProductAttributes(@Context final UriInfo uriInfo, + @PathParam("loanProductId") @Parameter(description = "loanProductId") final Long loanProductId, + @QueryParam("attributeKey") @Parameter(description = "attributeKey") final String attributeKey) { + platformUserRightsContext.isAuthenticated(); + + return externalAssetOwnerLoanProductAttributesReadService.retrieveAllLoanProductAttributesByLoanProductId(loanProductId, + attributeKey); + } + + @PUT + @Path("/{loanProductId}/attributes/{id}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = ExternalAssetOwnerLoanProductAttributesApiResourceSwagger.PutExternalAssetOwnerLoanProductAttributeRequest.class))) + @Operation(tags = { + "External Asset Owner Loan Product Attributes" }, summary = "Update a Loan Product Attribute", description = "Updates a loan product attribute with a given loan product id and attribute id", parameters = { + @Parameter(name = "loanProductId", description = "loanProductId"), + @Parameter(name = "attributeId", description = "attributeId") }) + @ApiResponses({ @ApiResponse(responseCode = "200", description = "A loan product attribute filtered by id is returned"), + @ApiResponse(responseCode = "404", description = "Not Found"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") }) + public CommandProcessingResult updateLoanProductAttribute( + @PathParam("loanProductId") @Parameter(description = "loanProductId") final Long loanProductId, + @PathParam("id") @Parameter(description = "attributeId") final Long attributeId, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + platformUserRightsContext.isAuthenticated(); + final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); + CommandWrapper request = builder.updateExternalAssetOwnerLoanProductAttribute(loanProductId, attributeId).build(); + + return commandsSourceWritePlatformService.logCommandSource(request); + } + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiResourceSwagger.java b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiResourceSwagger.java new file mode 100644 index 00000000000..a0db5f0b7f2 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnerLoanProductAttributesApiResourceSwagger.java @@ -0,0 +1,52 @@ +/** + * 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.investor.api; + +import io.swagger.v3.oas.annotations.media.Schema; + +@SuppressWarnings({ "MemberName" }) +final class ExternalAssetOwnerLoanProductAttributesApiResourceSwagger { + + private ExternalAssetOwnerLoanProductAttributesApiResourceSwagger() {} + + @Schema(description = "PostExternalAssetOwnerLoanProductAttributeRequest") + public static final class PostExternalAssetOwnerLoanProductAttributeRequest { + + private PostExternalAssetOwnerLoanProductAttributeRequest() {} + + @Schema(example = "SETTLEMENT_MODEL") + public String attributeKey; + + @Schema(example = "DELAYED_SETTLEMENT") + public String attributeValue; + } + + @Schema(description = "PutExternalAssetOwnerLoanProductAttributeRequest") + public static final class PutExternalAssetOwnerLoanProductAttributeRequest { + + private PutExternalAssetOwnerLoanProductAttributeRequest() {} + + @Schema(example = "SETTLEMENT_MODEL") + public String attributeKey; + + @Schema(example = "DELAYED_SETTLEMENT_DISABLED") + public String attributeValue; + } + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalAssetOwnerLoanProductAttributeRequestParameters.java b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalAssetOwnerLoanProductAttributeRequestParameters.java new file mode 100644 index 00000000000..a127b0fcd12 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalAssetOwnerLoanProductAttributeRequestParameters.java @@ -0,0 +1,27 @@ +/** + * 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.investor.data; + +public final class ExternalAssetOwnerLoanProductAttributeRequestParameters { + + private ExternalAssetOwnerLoanProductAttributeRequestParameters() {} + + public static final String ATTRIBUTE_KEY = "attributeKey"; + public static final String ATTRIBUTE_VALUE = "attributeValue"; +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferLoanProductAttributesData.java b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferLoanProductAttributesData.java new file mode 100644 index 00000000000..a1eaee87b2c --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/data/ExternalTransferLoanProductAttributesData.java @@ -0,0 +1,37 @@ +/** + * 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.investor.data; + +import java.io.Serializable; +import lombok.Data; +import lombok.Getter; + +/** + * Data object representing an external transfer loan product attribute + */ + +@Data +@Getter +public class ExternalTransferLoanProductAttributesData implements Serializable { + + private Long attributeId; + private Long loanProductId; + private String attributeKey; + private String attributeValue; +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/data/attribute/ExternalAssetOwnerLoanProductAttribute.java b/fineract-investor/src/main/java/org/apache/fineract/investor/data/attribute/ExternalAssetOwnerLoanProductAttribute.java new file mode 100644 index 00000000000..99b8a2d2e9e --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/data/attribute/ExternalAssetOwnerLoanProductAttribute.java @@ -0,0 +1,26 @@ +/** + * 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.investor.data.attribute; + +public interface ExternalAssetOwnerLoanProductAttribute { + + String getAttributeKey(); + + String getAttributeValue(); +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/data/attribute/SettlementModelExternalAssetOwnerLoanProductAttribute.java b/fineract-investor/src/main/java/org/apache/fineract/investor/data/attribute/SettlementModelExternalAssetOwnerLoanProductAttribute.java new file mode 100644 index 00000000000..d4dfa5e3356 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/data/attribute/SettlementModelExternalAssetOwnerLoanProductAttribute.java @@ -0,0 +1,42 @@ +/** + * 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.investor.data.attribute; + +public enum SettlementModelExternalAssetOwnerLoanProductAttribute implements ExternalAssetOwnerLoanProductAttribute { + + DEFAULT_SETTLEMENT("DEFAULT_SETTLEMENT"), DELAYED_SETTLEMENT("DELAYED_SETTLEMENT"),; + + private final String attributeKey; + private final String attributeValue; + + SettlementModelExternalAssetOwnerLoanProductAttribute(String attributeValue) { + this.attributeKey = "SETTLEMENT_MODEL"; + this.attributeValue = attributeValue; + } + + @Override + public String getAttributeKey() { + return attributeKey; + } + + @Override + public String getAttributeValue() { + return attributeValue; + } +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerLoanProductAttributes.java b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerLoanProductAttributes.java new file mode 100644 index 00000000000..80e538182eb --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerLoanProductAttributes.java @@ -0,0 +1,45 @@ +/** + * 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.investor.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; + +@Getter +@Setter +@Entity +@NoArgsConstructor +@Table(name = "m_external_asset_owner_loan_product_configurable_attributes") +public class ExternalAssetOwnerLoanProductAttributes extends AbstractAuditableWithUTCDateTimeCustom { + + @Column(name = "loan_product_id", nullable = false) + private Long loanProductId; + + @Column(name = "attribute_key", nullable = false) + private String attributeKey; + + @Column(name = "attribute_value", nullable = false) + private String attributeValue; + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerLoanProductAttributesRepository.java b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerLoanProductAttributesRepository.java new file mode 100644 index 00000000000..8b088de92e8 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/domain/ExternalAssetOwnerLoanProductAttributesRepository.java @@ -0,0 +1,31 @@ +/** + * 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.investor.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ExternalAssetOwnerLoanProductAttributesRepository extends JpaRepository, + JpaSpecificationExecutor { + + @Query("SELECT CASE WHEN COUNT(attribute)>0 THEN TRUE ELSE FALSE END FROM ExternalAssetOwnerLoanProductAttributes attribute WHERE attribute.loanProductId =:loanProductId AND attribute.attributeKey =:attributeKey") + boolean existsByLoanProductIdAndKey(@Param("loanProductId") Long loanProductId, @Param("attributeKey") String attributeKey); +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeAlreadyExistsException.java b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeAlreadyExistsException.java new file mode 100644 index 00000000000..0c8f42181a0 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeAlreadyExistsException.java @@ -0,0 +1,31 @@ +/** + * 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.investor.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; +import org.springframework.http.HttpStatus; + +public class ExternalAssetOwnerLoanProductAttributeAlreadyExistsException extends AbstractPlatformDomainRuleException { + + private static final String errorCode = "error.msg.externalAssetOwnerLoanProductAttribute.exists"; + + public ExternalAssetOwnerLoanProductAttributeAlreadyExistsException(String errorMessage) { + super(errorCode, errorMessage, HttpStatus.FORBIDDEN); + } +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException.java b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException.java new file mode 100644 index 00000000000..a9e90bcffad --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException.java @@ -0,0 +1,31 @@ +/** + * 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.investor.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException extends AbstractPlatformDomainRuleException { + + private static final String errorCode = "error.msg.externalAssetOwnerLoanProductAttribute.invalidSettlementAttribute"; + + public ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException(String errorMessage) { + super(errorCode, errorMessage); + } + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeNotFoundException.java b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeNotFoundException.java new file mode 100644 index 00000000000..342c82c3ced --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributeNotFoundException.java @@ -0,0 +1,31 @@ +/** + * 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.investor.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; +import org.springframework.http.HttpStatus; + +public class ExternalAssetOwnerLoanProductAttributeNotFoundException extends AbstractPlatformResourceNotFoundException { + + private static final String errorCode = "error.msg.externalAssetOwnerLoanProductAttribute.nonExistentAttribute"; + + public ExternalAssetOwnerLoanProductAttributeNotFoundException(final Long attributeId) { + super(errorCode, "Loan product attribute with id " + attributeId + " was not found", HttpStatus.NOT_FOUND); + } +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributesException.java b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributesException.java new file mode 100644 index 00000000000..4e47df7e167 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/exception/ExternalAssetOwnerLoanProductAttributesException.java @@ -0,0 +1,30 @@ +/** + * 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.investor.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class ExternalAssetOwnerLoanProductAttributesException extends AbstractPlatformDomainRuleException { + + private static final String errorCode = "error.msg.externalAssetOwnerLoanProductAttributes.general"; + + public ExternalAssetOwnerLoanProductAttributesException(String errorMessage) { + super(errorCode, errorMessage); + } +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/CreateExternalAssetOwnerLoanProductAttributeHandler.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/CreateExternalAssetOwnerLoanProductAttributeHandler.java new file mode 100644 index 00000000000..2261a318a89 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/CreateExternalAssetOwnerLoanProductAttributeHandler.java @@ -0,0 +1,39 @@ +/** + * 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.investor.service; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +@CommandType(entity = "EXTERNAL_ASSET_OWNER_LOAN_PRODUCT_ATTRIBUTE", action = "CREATE") +public class CreateExternalAssetOwnerLoanProductAttributeHandler implements NewCommandSourceHandler { + + private final ExternalAssetOwnerLoanProductAttributesWriteService externalAssetOwnerLoanProductAttributesWriteService; + + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return externalAssetOwnerLoanProductAttributesWriteService.createExternalAssetOwnerLoanProductAttribute(command); + } +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesMapper.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesMapper.java new file mode 100644 index 00000000000..68017a99a08 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesMapper.java @@ -0,0 +1,32 @@ +/** + * 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.investor.service; + +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.investor.data.ExternalTransferLoanProductAttributesData; +import org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributes; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(config = MapstructMapperConfig.class) +public interface ExternalAssetOwnerLoanProductAttributesMapper { + + @Mapping(target = "attributeId", source = "id") + ExternalTransferLoanProductAttributesData mapLoanProductAttributes(ExternalAssetOwnerLoanProductAttributes attributes); +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadService.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadService.java new file mode 100644 index 00000000000..4f76aeb3c3d --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadService.java @@ -0,0 +1,29 @@ +/** + * 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.investor.service; + +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.investor.data.ExternalTransferLoanProductAttributesData; + +public interface ExternalAssetOwnerLoanProductAttributesReadService { + + Page retrieveAllLoanProductAttributesByLoanProductId(Long loanProductId, + String attributeKey); + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadServiceImpl.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadServiceImpl.java new file mode 100644 index 00000000000..5206043af50 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadServiceImpl.java @@ -0,0 +1,86 @@ +/** + * 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.investor.service; + +import jakarta.persistence.criteria.Predicate; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.investor.data.ExternalTransferLoanProductAttributesData; +import org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributes; +import org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributesRepository; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; +import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ExternalAssetOwnerLoanProductAttributesReadServiceImpl implements ExternalAssetOwnerLoanProductAttributesReadService { + + private final ExternalAssetOwnerLoanProductAttributesRepository externalAssetOwnerLoanProductAttributesRepository; + private final LoanProductRepository loanProductRepository; + private final ExternalAssetOwnerLoanProductAttributesMapper mapper; + + @Override + @Cacheable(cacheNames = "externalAssetOwnerLoanProductAttributes", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#loanProductId.toString() + #attributeKey)", unless = "#attributeKey == null") + public Page retrieveAllLoanProductAttributesByLoanProductId(final Long loanProductId, + final String attributeKey) { + validateLoanProduct(loanProductId); + + PageRequest pageRequest = PageRequest.of(0, 100, Sort.by("id")); + + org.springframework.data.domain.Page pageOfAttributeData = externalAssetOwnerLoanProductAttributesRepository + .findAll(retrieveLoanProductAttributesByLoanProductIdAndAttributeKeySpecification(loanProductId, attributeKey), + pageRequest); + + return new Page<>(pageOfAttributeData.getContent().stream().map(mapper::mapLoanProductAttributes).toList(), + pageOfAttributeData.getNumberOfElements()); + } + + private void validateLoanProduct(final Long loanProductId) { + if (loanProductId == null) { + throw new IllegalArgumentException("At least one of the following parameters must be provided: loanProductId"); + } + if (!loanProductRepository.existsById(loanProductId)) { + throw new LoanProductNotFoundException(loanProductId); + } + } + + public static Specification retrieveLoanProductAttributesByLoanProductIdAndAttributeKeySpecification( + final Long loanProductId, final String attributeKey) { + return (root, query, cb) -> { + List predicates = new ArrayList<>(); + predicates.add(cb.equal(root.get("loanProductId"), loanProductId)); + + if (StringUtils.isNotBlank(attributeKey)) { + predicates.add(cb.equal(root.get("attributeKey"), attributeKey)); + } + + return cb.and(predicates.toArray(new Predicate[0])); + }; + } +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteService.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteService.java new file mode 100644 index 00000000000..2eb036f7fcd --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteService.java @@ -0,0 +1,30 @@ +/** + * 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.investor.service; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; + +public interface ExternalAssetOwnerLoanProductAttributesWriteService { + + CommandProcessingResult createExternalAssetOwnerLoanProductAttribute(JsonCommand command); + + CommandProcessingResult updateExternalAssetOwnerLoanProductAttribute(JsonCommand command); + +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteServiceImpl.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteServiceImpl.java new file mode 100644 index 00000000000..80be57b4087 --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteServiceImpl.java @@ -0,0 +1,192 @@ +/** + * 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.investor.service; + +import static org.reflections.scanners.Scanners.SubTypes; + +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; +import jakarta.transaction.Transactional; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import lombok.RequiredArgsConstructor; +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.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.investor.data.ExternalAssetOwnerLoanProductAttributeRequestParameters; +import org.apache.fineract.investor.data.attribute.ExternalAssetOwnerLoanProductAttribute; +import org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributes; +import org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributesRepository; +import org.apache.fineract.investor.exception.ExternalAssetOwnerLoanProductAttributeAlreadyExistsException; +import org.apache.fineract.investor.exception.ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException; +import org.apache.fineract.investor.exception.ExternalAssetOwnerLoanProductAttributeNotFoundException; +import org.apache.fineract.investor.exception.ExternalAssetOwnerLoanProductAttributesException; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; +import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; +import org.reflections.Reflections; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Transactional +public class ExternalAssetOwnerLoanProductAttributesWriteServiceImpl implements ExternalAssetOwnerLoanProductAttributesWriteService { + + private final FromJsonHelper fromApiJsonHelper; + private final ExternalAssetOwnerLoanProductAttributesRepository externalAssetOwnerLoanProductAttributesRepository; + private final LoanProductRepository loanProductRepository; + private final String INVESTOR_PATH = "org.apache.fineract.investor"; + private final Set> implementingClasses = new Reflections(INVESTOR_PATH) + .get(SubTypes.of(ExternalAssetOwnerLoanProductAttribute.class).asClass()); + + @Override + public CommandProcessingResult createExternalAssetOwnerLoanProductAttribute(JsonCommand command) { + final JsonElement json = fromApiJsonHelper.parse(command.json()); + String attributeKey = fromApiJsonHelper.extractStringNamed(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_KEY, + json); + String attributeValue = fromApiJsonHelper + .extractStringNamed(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_VALUE, json); + Long loanProductId = command.getProductId(); + validateLoanProductAttributeRequest(command.json(), attributeKey, attributeValue); + validateExternalAssetOwnerLoanProductAttribute(attributeKey, attributeValue); + validateLoanProductExistsAndAttributeDoesNotExist(loanProductId, attributeKey); + ExternalAssetOwnerLoanProductAttributes newAttribute = createExternalAssetOwnerLoanProductAttribute(loanProductId, attributeKey, + attributeValue); + externalAssetOwnerLoanProductAttributesRepository.saveAndFlush(newAttribute); + return buildResponseData(newAttribute); + } + + @Override + @CacheEvict(cacheNames = "externalAssetOwnerLoanProductAttributes", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#command.getProductId().toString() + #attributeKey)") + public CommandProcessingResult updateExternalAssetOwnerLoanProductAttribute(JsonCommand command) { + final JsonElement json = fromApiJsonHelper.parse(command.json()); + String attributeKey = fromApiJsonHelper.extractStringNamed(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_KEY, + json); + String attributeValue = fromApiJsonHelper + .extractStringNamed(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_VALUE, json); + + Long loanProductId = command.getProductId(); + Long attributeId = command.entityId(); + validateLoanProductAttributeRequest(command.json(), attributeKey, attributeValue); + validateExternalAssetOwnerLoanProductAttribute(attributeKey, attributeValue); + validateLoanProductExists(loanProductId); + ExternalAssetOwnerLoanProductAttributes attributeToUpdate = getLoanProductAttribute(attributeId); + validateLoanProductAttributeKeysMatch(attributeKey, attributeToUpdate.getAttributeKey()); + if (!attributeToUpdate.getAttributeValue().equals(attributeValue)) { + attributeToUpdate.setAttributeValue(attributeValue); + externalAssetOwnerLoanProductAttributesRepository.saveAndFlush(attributeToUpdate); + } + return buildResponseData(attributeToUpdate); + } + + private void validateLoanProductAttributeRequest(String apiRequestBodyAsJson, String attributeKey, String attributeValue) { + final Set requestParameters = new HashSet<>( + Arrays.asList(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_KEY, + ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_VALUE)); + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, apiRequestBodyAsJson, requestParameters); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanproductattribute"); + + baseDataValidator.reset().parameter(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_KEY).value(attributeKey) + .notBlank().notExceedingLengthOf(255); + + baseDataValidator.reset().parameter(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_VALUE).value(attributeValue) + .notBlank().notExceedingLengthOf(255); + + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", + dataValidationErrors); + } + } + + private void validateLoanProductExistsAndAttributeDoesNotExist(Long loanProductId, String attributeKey) { + validateLoanProductExists(loanProductId); + validateLoanProductAttributeDoesNotExist(loanProductId, attributeKey); + } + + private void validateLoanProductExists(Long loanProductId) { + if (!loanProductRepository.existsById(loanProductId)) { + throw new LoanProductNotFoundException(loanProductId); + } + } + + private void validateLoanProductAttributeDoesNotExist(Long loanProductId, String attributeKey) { + if (externalAssetOwnerLoanProductAttributesRepository.existsByLoanProductIdAndKey(loanProductId, attributeKey)) { + throw new ExternalAssetOwnerLoanProductAttributeAlreadyExistsException( + "attributeKey already exists for the loanProductId: " + loanProductId + ". Use PUT call to UPDATE the attribute."); + } + } + + private void validateLoanProductAttributeKeysMatch(String attributeKeyFromRequest, String attributeKeyFromDB) { + if (!attributeKeyFromRequest.equals(attributeKeyFromDB)) { + throw new ExternalAssetOwnerLoanProductAttributesException( + "The attribute key of requested update attribute does not match the attribute key from database."); + } + } + + private void validateExternalAssetOwnerLoanProductAttribute(String attributeKey, String attributeValue) { + for (Class implementingClass : implementingClasses) { + if (implementingClass.isEnum()) { + for (Object obj : implementingClass.getEnumConstants()) { + ExternalAssetOwnerLoanProductAttribute objEnum = (ExternalAssetOwnerLoanProductAttribute) obj; + if (objEnum.getAttributeKey().equals(attributeKey) + && objEnum.getAttributeValue().equals(attributeValue.toUpperCase())) { + return; + } + } + } + } + throw new ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException( + "The given attribute key or attribute value is not valid."); + } + + private ExternalAssetOwnerLoanProductAttributes getLoanProductAttribute(Long attributeId) { + Optional loanProductAttribute = externalAssetOwnerLoanProductAttributesRepository + .findById(attributeId); + if (loanProductAttribute.isEmpty()) { + throw new ExternalAssetOwnerLoanProductAttributeNotFoundException(attributeId); + } + return loanProductAttribute.get(); + } + + private ExternalAssetOwnerLoanProductAttributes createExternalAssetOwnerLoanProductAttribute(Long loanProductId, String attributeKey, + String attributeValue) { + ExternalAssetOwnerLoanProductAttributes attribute = new ExternalAssetOwnerLoanProductAttributes(); + attribute.setLoanProductId(loanProductId); + attribute.setAttributeKey(attributeKey); + attribute.setAttributeValue(attributeValue); + return attribute; + } + + private CommandProcessingResult buildResponseData(ExternalAssetOwnerLoanProductAttributes savedAttribute) { + return new CommandProcessingResultBuilder().withEntityId(savedAttribute.getLoanProductId()).build(); + } +} diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/service/UpdateExternalAssetOwnerLoanProductAttributeHandler.java b/fineract-investor/src/main/java/org/apache/fineract/investor/service/UpdateExternalAssetOwnerLoanProductAttributeHandler.java new file mode 100644 index 00000000000..3374d67863f --- /dev/null +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/service/UpdateExternalAssetOwnerLoanProductAttributeHandler.java @@ -0,0 +1,39 @@ +/** + * 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.investor.service; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +@CommandType(entity = "EXTERNAL_ASSET_OWNER_LOAN_PRODUCT_ATTRIBUTE", action = "UPDATE") +public class UpdateExternalAssetOwnerLoanProductAttributeHandler implements NewCommandSourceHandler { + + private final ExternalAssetOwnerLoanProductAttributesWriteService externalAssetOwnerLoanProductAttributesWriteService; + + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + return externalAssetOwnerLoanProductAttributesWriteService.updateExternalAssetOwnerLoanProductAttribute(command); + } +} diff --git a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml index 39ea60c9802..e2d1717dcdd 100644 --- a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml +++ b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml @@ -35,4 +35,5 @@ + diff --git a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0014_add_external_asset_owner_loan_product_configurable_attributes.xml b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0014_add_external_asset_owner_loan_product_configurable_attributes.xml new file mode 100644 index 00000000000..bcbc50e3d33 --- /dev/null +++ b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0014_add_external_asset_owner_loan_product_configurable_attributes.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadServiceTest.java b/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadServiceTest.java new file mode 100644 index 00000000000..9286f8c7c54 --- /dev/null +++ b/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesReadServiceTest.java @@ -0,0 +1,164 @@ +/** + * 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.investor.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.stream.Stream; +import org.apache.fineract.infrastructure.core.service.Page; +import org.apache.fineract.investor.data.ExternalTransferLoanProductAttributesData; +import org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributes; +import org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributesRepository; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; +import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; + +@ExtendWith(MockitoExtension.class) +public class ExternalAssetOwnerLoanProductAttributesReadServiceTest { + + @Mock + private ExternalAssetOwnerLoanProductAttributesRepository externalAssetOwnerLoanProductAttributesRepository; + + @Mock + private LoanProductRepository loanProductRepository; + + @Mock + private ExternalAssetOwnerLoanProductAttributesMapper mapper; + + private ExternalAssetOwnerLoanProductAttributesReadService underTest; + + private int offset = 0; + + private int limit = 100; + + @BeforeEach + public void setUp() { + underTest = new ExternalAssetOwnerLoanProductAttributesReadServiceImpl(externalAssetOwnerLoanProductAttributesRepository, + loanProductRepository, mapper); + } + + @ParameterizedTest + @MethodSource("testRetrieveAllLoanProductAttributesByLoanProductIdDataProvider") + public void testRetrieveAllLoanProductAttributesByLoanProductId(Long loanProductId, String attributeKey) { + // given + ExternalAssetOwnerLoanProductAttributes attributes = Mockito.mock(ExternalAssetOwnerLoanProductAttributes.class); + ExternalTransferLoanProductAttributesData data = Mockito.mock(ExternalTransferLoanProductAttributesData.class); + PageRequest pageRequest = PageRequest.of(offset, limit, Sort.by("id").ascending()); + org.springframework.data.domain.Page attributesPage = new PageImpl<>(List.of(attributes), + pageRequest, 1); + + when(externalAssetOwnerLoanProductAttributesRepository.findAll(any(Specification.class), eq(pageRequest))) + .thenReturn(attributesPage); + when(loanProductRepository.existsById(loanProductId)).thenReturn(true); + when(mapper.mapLoanProductAttributes(attributes)).thenReturn(data); + + // when + Page result = underTest.retrieveAllLoanProductAttributesByLoanProductId(loanProductId, + attributeKey); + + // then + assertEquals(1, result.getTotalFilteredRecords()); + assertEquals(data, result.getPageItems().get(0)); + verify(loanProductRepository, times(1)).existsById(loanProductId); + verify(mapper, times(1)).mapLoanProductAttributes(attributes); + } + + @Test + public void testRetrieveAllLoanProductAttributesByLoanProductIdAndInvalidAttributeKey() { + // given + Long loanProductId = 1L; + ExternalAssetOwnerLoanProductAttributes attributes = Mockito.mock(ExternalAssetOwnerLoanProductAttributes.class); + PageRequest pageRequest = PageRequest.of(offset, limit, Sort.by("id").ascending()); + org.springframework.data.domain.Page attributesPage = new PageImpl<>(List.of(attributes), + pageRequest, 0); + + when(externalAssetOwnerLoanProductAttributesRepository.findAll(any(Specification.class), eq(pageRequest))) + .thenReturn(attributesPage); + when(loanProductRepository.existsById(loanProductId)).thenReturn(true); + when(mapper.mapLoanProductAttributes(attributes)).thenReturn(null); + + // when + Page result = underTest.retrieveAllLoanProductAttributesByLoanProductId(loanProductId, + "BAD_KEY"); + + // then + assertEquals(1, result.getTotalFilteredRecords()); + assertNull(result.getPageItems().get(0)); + verify(externalAssetOwnerLoanProductAttributesRepository, times(1)).findAll(any(Specification.class), eq(pageRequest)); + verify(loanProductRepository, times(1)).existsById(loanProductId); + verify(mapper, times(1)).mapLoanProductAttributes(attributes); + } + + @Test + public void testRetrieveLoanProductAttributesDataByLoanProductIdIllegalArgument() { + // given + + // when + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> underTest.retrieveAllLoanProductAttributesByLoanProductId(null, null)); + + // then + assertEquals("At least one of the following parameters must be provided: loanProductId", exception.getMessage()); + verifyNoInteractions(externalAssetOwnerLoanProductAttributesRepository); + verifyNoInteractions(loanProductRepository); + verifyNoInteractions(mapper); + } + + @Test + public void testRetrieveLoanProductAttributesDataByLoanProductIdNotFound() { + // given + Long loanProductId = 1L; + when(loanProductRepository.existsById(loanProductId)).thenReturn(false); + + // when + LoanProductNotFoundException exception = assertThrows(LoanProductNotFoundException.class, + () -> underTest.retrieveAllLoanProductAttributesByLoanProductId(loanProductId, null)); + + // then + assertEquals("Loan product with identifier 1 does not exist", exception.getMessage()); + verify(loanProductRepository, times(1)).existsById(loanProductId); + verifyNoInteractions(externalAssetOwnerLoanProductAttributesRepository); + verifyNoInteractions(mapper); + } + + private static Stream testRetrieveAllLoanProductAttributesByLoanProductIdDataProvider() { + return Stream.of(Arguments.of(1L, "SETTLEMENT_MODEL"), Arguments.of(1L, null)); + } +} diff --git a/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteServiceImplTest.java b/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteServiceImplTest.java new file mode 100644 index 00000000000..214226ca2f2 --- /dev/null +++ b/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnerLoanProductAttributesWriteServiceImplTest.java @@ -0,0 +1,389 @@ +/** + * 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.investor.service; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.gson.JsonElement; +import java.util.Optional; +import java.util.stream.Stream; +import lombok.Setter; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.investor.data.ExternalAssetOwnerLoanProductAttributeRequestParameters; +import org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributes; +import org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributesRepository; +import org.apache.fineract.investor.exception.ExternalAssetOwnerLoanProductAttributeAlreadyExistsException; +import org.apache.fineract.investor.exception.ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException; +import org.apache.fineract.investor.exception.ExternalAssetOwnerLoanProductAttributeNotFoundException; +import org.apache.fineract.investor.exception.ExternalAssetOwnerLoanProductAttributesException; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; +import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; +import org.junit.Assert; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class ExternalAssetOwnerLoanProductAttributesWriteServiceImplTest { + + @Test + public void testCreateExternalAssetOwnerLoanProductAttributeHappyPath() { + TestContext testContext = new TestContext(true); + ArgumentCaptor loanProductAttributeArgumentCaptor = ArgumentCaptor + .forClass(ExternalAssetOwnerLoanProductAttributes.class); + + // given + final JsonCommand command = createJsonCommand(testContext.jsonCommandString, testContext.loanProductId, null); + when(testContext.externalAssetOwnerLoanProductAttributesRepository.existsByLoanProductIdAndKey(testContext.loanProductId, + testContext.attributeKey)).thenReturn(false); + when(testContext.loanProductRepository.existsById(testContext.loanProductId)).thenReturn(true); + + // when + testContext.externalAssetOwnerLoanProductAttributesWriteService.createExternalAssetOwnerLoanProductAttribute(command); + + // then + verify(testContext.externalAssetOwnerLoanProductAttributesRepository).existsByLoanProductIdAndKey(any(), any()); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository).saveAndFlush(loanProductAttributeArgumentCaptor.capture()); + verify(testContext.loanProductRepository).existsById(testContext.loanProductId); + assertLoanProductAttributeValues(testContext, loanProductAttributeArgumentCaptor.getValue()); + } + + @Test + public void testUpdateExternalAssetOwnerLoanProductAttributeHappyPath() { + TestContext testContext = new TestContext(false); + ArgumentCaptor loanProductAttributeArgumentCaptor = ArgumentCaptor + .forClass(ExternalAssetOwnerLoanProductAttributes.class); + + ExternalAssetOwnerLoanProductAttributes attributeInDB = new ExternalAssetOwnerLoanProductAttributes(); + attributeInDB.setLoanProductId(testContext.loanProductId); + attributeInDB.setAttributeKey(testContext.attributeKey); + attributeInDB.setAttributeValue("DIFFERENT_VALUE"); + attributeInDB.setId(1L); + + // given + final JsonCommand command = createJsonCommand(testContext.jsonCommandString, testContext.loanProductId, attributeInDB.getId()); + when(testContext.loanProductRepository.existsById(testContext.loanProductId)).thenReturn(true); + when(testContext.externalAssetOwnerLoanProductAttributesRepository.findById(command.entityId())) + .thenReturn(Optional.of(attributeInDB)); + + testContext.externalAssetOwnerLoanProductAttributesWriteService.updateExternalAssetOwnerLoanProductAttribute(command); + + // then + verify(testContext.loanProductRepository).existsById(testContext.loanProductId); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository).findById(eq(command.entityId())); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository).saveAndFlush(loanProductAttributeArgumentCaptor.capture()); + } + + @Test + public void testUpdateExternalAssetOwnerLoanProductAttributeUpdateNotRequired() { + TestContext testContext = new TestContext(false); + ArgumentCaptor loanProductAttributeArgumentCaptor = ArgumentCaptor + .forClass(ExternalAssetOwnerLoanProductAttributes.class); + + ExternalAssetOwnerLoanProductAttributes attributeInDB = new ExternalAssetOwnerLoanProductAttributes(); + attributeInDB.setLoanProductId(testContext.loanProductId); + attributeInDB.setAttributeKey(testContext.attributeKey); + attributeInDB.setAttributeValue(testContext.attributeValue); + attributeInDB.setId(1L); + + // given + final JsonCommand command = createJsonCommand(testContext.jsonCommandString, testContext.loanProductId, attributeInDB.getId()); + when(testContext.loanProductRepository.existsById(testContext.loanProductId)).thenReturn(true); + when(testContext.externalAssetOwnerLoanProductAttributesRepository.findById(command.entityId())) + .thenReturn(Optional.of(attributeInDB)); + + testContext.externalAssetOwnerLoanProductAttributesWriteService.updateExternalAssetOwnerLoanProductAttribute(command); + + // then + verify(testContext.loanProductRepository).existsById(testContext.loanProductId); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository).findById(eq(command.entityId())); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)) + .saveAndFlush(loanProductAttributeArgumentCaptor.capture()); + } + + @Test + public void testUpdateExternalAssetOwnerLoanProductAttributeOnAttributeThatDoesNotExist() { + TestContext testContext = new TestContext(false); + ArgumentCaptor loanProductAttributeArgumentCaptor = ArgumentCaptor + .forClass(ExternalAssetOwnerLoanProductAttributes.class); + + // given + final JsonCommand command = createJsonCommand(testContext.jsonCommandString, testContext.loanProductId, 1L); + when(testContext.loanProductRepository.existsById(testContext.loanProductId)).thenReturn(true); + when(testContext.externalAssetOwnerLoanProductAttributesRepository.findById(1L)).thenReturn(Optional.empty()); + + ExternalAssetOwnerLoanProductAttributeNotFoundException thrownException = Assert.assertThrows( + ExternalAssetOwnerLoanProductAttributeNotFoundException.class, + () -> testContext.externalAssetOwnerLoanProductAttributesWriteService + .updateExternalAssetOwnerLoanProductAttribute(command)); + + // then + verify(testContext.loanProductRepository).existsById(testContext.loanProductId); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository).findById(eq(1L)); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)) + .saveAndFlush(loanProductAttributeArgumentCaptor.capture()); + Assertions.assertEquals(thrownException.getMessage(), "Loan product attribute with id " + 1L + " was not found"); + } + + @Test + public void testUpdateExternalAssetOwnerLoanProductAttributeOnAttributeWithDifferentKeyValue() { + TestContext testContext = new TestContext(false); + ArgumentCaptor loanProductAttributeArgumentCaptor = ArgumentCaptor + .forClass(ExternalAssetOwnerLoanProductAttributes.class); + + ExternalAssetOwnerLoanProductAttributes attributeInDB = new ExternalAssetOwnerLoanProductAttributes(); + attributeInDB.setLoanProductId(testContext.loanProductId); + attributeInDB.setAttributeKey("DIFFERENT_KEY"); + attributeInDB.setAttributeValue(testContext.attributeValue); + attributeInDB.setId(1L); + + // given + final JsonCommand command = createJsonCommand(testContext.jsonCommandString, testContext.loanProductId, attributeInDB.getId()); + when(testContext.loanProductRepository.existsById(testContext.loanProductId)).thenReturn(true); + when(testContext.externalAssetOwnerLoanProductAttributesRepository.findById(command.entityId())) + .thenReturn(Optional.of(attributeInDB)); + + ExternalAssetOwnerLoanProductAttributesException thrownException = Assert.assertThrows( + ExternalAssetOwnerLoanProductAttributesException.class, + () -> testContext.externalAssetOwnerLoanProductAttributesWriteService + .updateExternalAssetOwnerLoanProductAttribute(command)); + + // then + verify(testContext.loanProductRepository).existsById(testContext.loanProductId); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository).findById(eq(command.entityId())); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)) + .saveAndFlush(loanProductAttributeArgumentCaptor.capture()); + Assertions.assertEquals(thrownException.getMessage(), + "The attribute key of requested update attribute does not match the attribute key from database."); + } + + @Test + public void testCreateExternalAssetOwnerLoanProductAttributeUsingDefaultSettlementValue() { + TestContext testContext = new TestContext(true); + ArgumentCaptor loanProductAttributeArgumentCaptor = ArgumentCaptor + .forClass(ExternalAssetOwnerLoanProductAttributes.class); + + // given + final JsonElement jsonCommandElement = testContext.fromJsonHelper.parse(testContext.jsonCommandString); + final JsonCommand command = createJsonCommand(testContext.jsonCommandString, testContext.loanProductId, null); + when(testContext.externalAssetOwnerLoanProductAttributesRepository.existsByLoanProductIdAndKey(testContext.loanProductId, + testContext.attributeKey)).thenReturn(false); + when(testContext.loanProductRepository.existsById(testContext.loanProductId)).thenReturn(true); + when(testContext.fromApiJsonHelper.extractStringNamed(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_VALUE, + jsonCommandElement)).thenReturn("DEFAULT_SETTLEMENT"); + testContext.setAttributeValue("DEFAULT_SETTLEMENT"); + + // when + testContext.externalAssetOwnerLoanProductAttributesWriteService.createExternalAssetOwnerLoanProductAttribute(command); + + // then + verify(testContext.externalAssetOwnerLoanProductAttributesRepository).existsByLoanProductIdAndKey(any(), any()); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository).saveAndFlush(loanProductAttributeArgumentCaptor.capture()); + verify(testContext.loanProductRepository).existsById(testContext.loanProductId); + assertLoanProductAttributeValues(testContext, loanProductAttributeArgumentCaptor.getValue()); + } + + @ParameterizedTest + @MethodSource("externalAssetOwnerLoanProductAttributeApiRequestDataValidationErrors") + public void testExternalAssetOwnerLoanProductAttributeRequestWithApiDataValidationErrors(String testName, String attributeKey, + String attributeValue, String expectedErrorString) { + TestContext testContext = new TestContext(true); + final JsonElement jsonCommandElement = testContext.fromJsonHelper.parse(testContext.jsonCommandString); + final JsonCommand command = createJsonCommand(testContext.jsonCommandString, testContext.loanProductId, null); + when(testContext.fromApiJsonHelper.extractStringNamed(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_KEY, + jsonCommandElement)).thenReturn(attributeKey); + when(testContext.fromApiJsonHelper.extractStringNamed(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_VALUE, + jsonCommandElement)).thenReturn(attributeValue); + + PlatformApiDataValidationException thrownException = Assert.assertThrows(PlatformApiDataValidationException.class, + () -> testContext.externalAssetOwnerLoanProductAttributesWriteService + .createExternalAssetOwnerLoanProductAttribute(command)); + + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)).saveAndFlush(any()); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)).existsByLoanProductIdAndKey(any(), any()); + verify(testContext.loanProductRepository, times(0)).existsById(testContext.loanProductId); + Assertions.assertEquals(thrownException.getMessage(), expectedErrorString); + } + + @Test + public void testCreateLoanProductAttributeExternalAssetOwnerExternalAssetOwnerLoanProductNotFound() { + TestContext testContext = new TestContext(true); + final JsonCommand command = createJsonCommand(testContext.jsonCommandString, testContext.loanProductId, null); + + when(testContext.loanProductRepository.existsById(testContext.loanProductId)).thenReturn(false); + + LoanProductNotFoundException thrownException = Assert.assertThrows(LoanProductNotFoundException.class, + () -> testContext.externalAssetOwnerLoanProductAttributesWriteService + .createExternalAssetOwnerLoanProductAttribute(command)); + + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)).saveAndFlush(any()); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)).existsByLoanProductIdAndKey(any(), any()); + verify(testContext.loanProductRepository, times(1)).existsById(testContext.loanProductId); + Assertions.assertEquals(thrownException.getMessage(), + "Loan product with identifier " + testContext.loanProductId + " does not exist"); + } + + @Test + public void testCreateLoanProductAttributeExternalAssetOwnerExternalAssetOwnerLoanProductAlreadyHasAttribute() { + TestContext testContext = new TestContext(true); + final JsonCommand command = createJsonCommand(testContext.jsonCommandString, testContext.loanProductId, null); + + when(testContext.externalAssetOwnerLoanProductAttributesRepository.existsByLoanProductIdAndKey(testContext.loanProductId, + testContext.attributeKey)).thenReturn(true); + when(testContext.loanProductRepository.existsById(testContext.loanProductId)).thenReturn(true); + + ExternalAssetOwnerLoanProductAttributeAlreadyExistsException thrownException = Assert.assertThrows( + ExternalAssetOwnerLoanProductAttributeAlreadyExistsException.class, + () -> testContext.externalAssetOwnerLoanProductAttributesWriteService + .createExternalAssetOwnerLoanProductAttribute(command)); + + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)).saveAndFlush(any()); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(1)).existsByLoanProductIdAndKey(any(), any()); + verify(testContext.loanProductRepository, times(1)).existsById(testContext.loanProductId); + Assertions.assertEquals(thrownException.getMessage(), "attributeKey already exists for the loanProductId: " + + testContext.loanProductId + ". Use PUT call to UPDATE the attribute."); + } + + @Test + public void testExternalAssetOwnerLoanProductAttributeInvalidKey() { + TestContext testContext = new TestContext(true); + + final JsonCommand command = createJsonCommand(testContext.jsonCommandString, testContext.loanProductId, null); + + final JsonElement jsonCommandElement = testContext.fromJsonHelper.parse(testContext.jsonCommandString); + when(testContext.fromApiJsonHelper.extractStringNamed(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_KEY, + jsonCommandElement)).thenReturn("BAD_KEY"); + ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException thrownException = Assert.assertThrows( + ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException.class, + () -> testContext.externalAssetOwnerLoanProductAttributesWriteService + .createExternalAssetOwnerLoanProductAttribute(command)); + + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)).saveAndFlush(any()); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)).existsByLoanProductIdAndKey(any(), any()); + verify(testContext.loanProductRepository, times(0)).existsById(testContext.loanProductId); + Assertions.assertEquals(thrownException.getMessage(), "The given attribute key or attribute value is not valid."); + } + + @Test + public void testExternalAssetOwnerLoanProductAttributeInvalidValue() { + TestContext testContext = new TestContext(true); + + final JsonCommand command = createJsonCommand(testContext.jsonCommandString, testContext.loanProductId, null); + + final JsonElement jsonCommandElement = testContext.fromJsonHelper.parse(testContext.jsonCommandString); + when(testContext.fromApiJsonHelper.extractStringNamed(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_VALUE, + jsonCommandElement)).thenReturn("BAD_VALUE"); + ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException thrownException = Assert.assertThrows( + ExternalAssetOwnerLoanProductAttributeInvalidSettlementAttributeException.class, + () -> testContext.externalAssetOwnerLoanProductAttributesWriteService + .createExternalAssetOwnerLoanProductAttribute(command)); + + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)).saveAndFlush(any()); + verify(testContext.externalAssetOwnerLoanProductAttributesRepository, times(0)).existsByLoanProductIdAndKey(any(), any()); + verify(testContext.loanProductRepository, times(0)).existsById(testContext.loanProductId); + Assertions.assertEquals(thrownException.getMessage(), "The given attribute key or attribute value is not valid."); + } + + private static Stream externalAssetOwnerLoanProductAttributeApiRequestDataValidationErrors() { + + return Stream.of(Arguments.of("blankAttributeValue", "SETTLEMENT_MODEL", "", "Validation errors exist."), + Arguments.of("blankAttributeKey", "", "DELAYED_SETTLEMENT", "Validation errors exist.")); + } + + private void assertLoanProductAttributeValues(final TestContext testContext, + final ExternalAssetOwnerLoanProductAttributes loanProductAttribute) { + Assertions.assertEquals(testContext.loanProductId, loanProductAttribute.getLoanProductId()); + Assertions.assertEquals(testContext.attributeKey, loanProductAttribute.getAttributeKey()); + Assertions.assertEquals(testContext.attributeValue, loanProductAttribute.getAttributeValue()); + } + + /** + * Helper method to create {@link JsonCommand} object from json command string. + * + * @param jsonCommand + * the json command string + * @param loanProductId + * the loanProductId + * @return the {@link JsonCommand} object. + */ + private JsonCommand createJsonCommand(final String jsonCommand, final Long loanProductId, final Long resourceId) { + return new JsonCommand(null, jsonCommand, null, null, null, resourceId, null, null, null, null, null, null, null, loanProductId, + null, null, null); + } + + static class TestContext { + + @Mock + private FromJsonHelper fromApiJsonHelper; + + @Mock + private ExternalAssetOwnerLoanProductAttributesRepository externalAssetOwnerLoanProductAttributesRepository; + + @Mock + private LoanProductRepository loanProductRepository; + + @InjectMocks + private ExternalAssetOwnerLoanProductAttributesWriteServiceImpl externalAssetOwnerLoanProductAttributesWriteService; + + private final FromJsonHelper fromJsonHelper = new FromJsonHelper(); + private final Long loanProductId = Long.valueOf(RandomStringUtils.randomNumeric(2)); + @Setter + private String attributeKey = "SETTLEMENT_MODEL"; + @Setter + private String attributeValue = "DELAYED_SETTLEMENT"; + private String jsonCommandString = String.format(""" + { + "attributeKey": "%s", + "attributeValue": "%s" + } + """, attributeKey, attributeValue); + + TestContext(boolean stubFromApiJsonHelper) { + MockitoAnnotations.openMocks(this); + if (stubFromApiJsonHelper) { + stubFromApiJsonHelper(); + } + } + + private void stubFromApiJsonHelper() { + final JsonElement jsonCommandElement = fromJsonHelper.parse(jsonCommandString); + when(fromApiJsonHelper.parse(anyString())).thenReturn(jsonCommandElement); + when(fromApiJsonHelper.extractStringNamed(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_KEY, + jsonCommandElement)).thenReturn(attributeKey); + when(fromApiJsonHelper.extractStringNamed(ExternalAssetOwnerLoanProductAttributeRequestParameters.ATTRIBUTE_VALUE, + jsonCommandElement)).thenReturn(attributeValue); + } + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java index 68e912b04a5..f8f94a336c7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java @@ -21,6 +21,7 @@ import java.util.List; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket; +import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; @@ -36,4 +37,8 @@ public interface LoanProductRepository extends JpaRepository, List findByDelinquencyBucketNotNull(); LoanProduct findByExternalId(ExternalId externalId); + + @Override + @Query("SELECT CASE WHEN COUNT(loanProduct)>0 THEN TRUE ELSE FALSE END FROM LoanProduct loanProduct WHERE loanProduct.id = :loanProductId") + boolean existsById(@NotNull @Param("loanProductId") Long loanProductId); }