Skip to content

Commit

Permalink
FINERACT-2081: Ability to assign settlement information to loan produ…
Browse files Browse the repository at this point in the history
…ct when asset is externally owned
  • Loading branch information
galovics committed Jan 13, 2025
1 parent 3481761 commit 5f64b56
Show file tree
Hide file tree
Showing 31 changed files with 1,715 additions and 5 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions fineract-investor/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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',

Expand All @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(id, loanProductIdParamName, attributeKeyParamName, attributeValueParamName));
}
Original file line number Diff line number Diff line change
@@ -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<ExternalTransferLoanProductAttributesData> 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<ExternalTransferLoanProductAttributesData> 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);
}

}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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";
}
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 5f64b56

Please sign in to comment.