FINERACT-2418: add origination api implementation

This commit is contained in:
Attila Budai
2026-01-28 17:01:57 +01:00
committed by Arnold Gálovics
parent ba978c591b
commit 82e6a237a6
23 changed files with 1612 additions and 19 deletions
@@ -96,6 +96,7 @@ import org.apache.fineract.client.feign.services.LoanCollateralApi;
import org.apache.fineract.client.feign.services.LoanCollateralManagementApi; import org.apache.fineract.client.feign.services.LoanCollateralManagementApi;
import org.apache.fineract.client.feign.services.LoanDisbursementDetailsApi; import org.apache.fineract.client.feign.services.LoanDisbursementDetailsApi;
import org.apache.fineract.client.feign.services.LoanInterestPauseApi; import org.apache.fineract.client.feign.services.LoanInterestPauseApi;
import org.apache.fineract.client.feign.services.LoanOriginatorsApi;
import org.apache.fineract.client.feign.services.LoanProductsApi; import org.apache.fineract.client.feign.services.LoanProductsApi;
import org.apache.fineract.client.feign.services.LoanReschedulingApi; import org.apache.fineract.client.feign.services.LoanReschedulingApi;
import org.apache.fineract.client.feign.services.LoanTransactionsApi; import org.apache.fineract.client.feign.services.LoanTransactionsApi;
@@ -532,6 +533,10 @@ public final class FineractFeignClient {
return create(LoanInterestPauseApi.class); return create(LoanInterestPauseApi.class);
} }
public LoanOriginatorsApi loanOriginators() {
return create(LoanOriginatorsApi.class);
}
public LoanProductsApi loanProducts() { public LoanProductsApi loanProducts() {
return create(LoanProductsApi.class); return create(LoanProductsApi.class);
} }
@@ -20,6 +20,10 @@ package org.apache.fineract.portfolio.loanorigination.api;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
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.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Consumes;
@@ -58,7 +62,8 @@ public class LoanOriginatorApiResource {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create a new loan originator", description = "Creates a new loan originator record. Requires CREATE_LOAN_ORIGINATOR permission.") @Operation(summary = "Create a new loan originator", description = "Creates a new loan originator record. Requires CREATE_LOAN_ORIGINATOR permission.")
@ApiResponse(responseCode = "200", description = "OK") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.PostLoanOriginatorsRequest.class)))
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.PostLoanOriginatorsResponse.class)))
@ApiResponse(responseCode = "400", description = "Required parameter is missing or incorrect format") @ApiResponse(responseCode = "400", description = "Required parameter is missing or incorrect format")
@ApiResponse(responseCode = "403", description = "Duplicate external ID or insufficient permissions") @ApiResponse(responseCode = "403", description = "Duplicate external ID or insufficient permissions")
public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) { public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
@@ -70,7 +75,7 @@ public class LoanOriginatorApiResource {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "List all loan originators", description = "Retrieves all loan originator records. Requires READ_LOAN_ORIGINATOR permission.") @Operation(summary = "List all loan originators", description = "Retrieves all loan originator records. Requires READ_LOAN_ORIGINATOR permission.")
@ApiResponse(responseCode = "200", description = "OK") @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.GetLoanOriginatorsResponse.class))))
@ApiResponse(responseCode = "403", description = "Insufficient permissions") @ApiResponse(responseCode = "403", description = "Insufficient permissions")
public List<LoanOriginatorData> retrieveAll() { public List<LoanOriginatorData> retrieveAll() {
this.context.authenticatedUser().validateHasReadPermission(LoanOriginatorApiConstants.RESOURCE_NAME); this.context.authenticatedUser().validateHasReadPermission(LoanOriginatorApiConstants.RESOURCE_NAME);
@@ -83,7 +88,7 @@ public class LoanOriginatorApiResource {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a loan originator by ID", description = "Retrieves a loan originator by its internal ID. Requires READ_LOAN_ORIGINATOR permission.") @Operation(summary = "Retrieve a loan originator by ID", description = "Retrieves a loan originator by its internal ID. Requires READ_LOAN_ORIGINATOR permission.")
@ApiResponse(responseCode = "200", description = "OK") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.GetLoanOriginatorsResponse.class)))
@ApiResponse(responseCode = "403", description = "Insufficient permissions") @ApiResponse(responseCode = "403", description = "Insufficient permissions")
@ApiResponse(responseCode = "404", description = "Originator not found") @ApiResponse(responseCode = "404", description = "Originator not found")
public LoanOriginatorData retrieveOne(@PathParam("originatorId") @Parameter(description = "originatorId") final Long originatorId) { public LoanOriginatorData retrieveOne(@PathParam("originatorId") @Parameter(description = "originatorId") final Long originatorId) {
@@ -97,7 +102,7 @@ public class LoanOriginatorApiResource {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a loan originator by external ID", description = "Retrieves a loan originator by its external ID. Requires READ_LOAN_ORIGINATOR permission.") @Operation(summary = "Retrieve a loan originator by external ID", description = "Retrieves a loan originator by its external ID. Requires READ_LOAN_ORIGINATOR permission.")
@ApiResponse(responseCode = "200", description = "OK") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.GetLoanOriginatorsResponse.class)))
@ApiResponse(responseCode = "403", description = "Insufficient permissions") @ApiResponse(responseCode = "403", description = "Insufficient permissions")
@ApiResponse(responseCode = "404", description = "Originator not found") @ApiResponse(responseCode = "404", description = "Originator not found")
public LoanOriginatorData retrieveByExternalId( public LoanOriginatorData retrieveByExternalId(
@@ -112,7 +117,8 @@ public class LoanOriginatorApiResource {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update a loan originator by ID", description = "Updates a loan originator by its internal ID. Requires UPDATE_LOAN_ORIGINATOR permission.") @Operation(summary = "Update a loan originator by ID", description = "Updates a loan originator by its internal ID. Requires UPDATE_LOAN_ORIGINATOR permission.")
@ApiResponse(responseCode = "200", description = "OK") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.PutLoanOriginatorsRequest.class)))
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.PutLoanOriginatorsResponse.class)))
@ApiResponse(responseCode = "400", description = "Incorrect format") @ApiResponse(responseCode = "400", description = "Incorrect format")
@ApiResponse(responseCode = "403", description = "Insufficient permissions") @ApiResponse(responseCode = "403", description = "Insufficient permissions")
@ApiResponse(responseCode = "404", description = "Originator not found") @ApiResponse(responseCode = "404", description = "Originator not found")
@@ -128,7 +134,8 @@ public class LoanOriginatorApiResource {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update a loan originator by external ID", description = "Updates a loan originator by its external ID. Requires UPDATE_LOAN_ORIGINATOR permission.") @Operation(summary = "Update a loan originator by external ID", description = "Updates a loan originator by its external ID. Requires UPDATE_LOAN_ORIGINATOR permission.")
@ApiResponse(responseCode = "200", description = "OK") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.PutLoanOriginatorsRequest.class)))
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.PutLoanOriginatorsResponse.class)))
@ApiResponse(responseCode = "400", description = "Incorrect format") @ApiResponse(responseCode = "400", description = "Incorrect format")
@ApiResponse(responseCode = "403", description = "Insufficient permissions") @ApiResponse(responseCode = "403", description = "Insufficient permissions")
@ApiResponse(responseCode = "404", description = "Originator not found") @ApiResponse(responseCode = "404", description = "Originator not found")
@@ -147,7 +154,7 @@ public class LoanOriginatorApiResource {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete a loan originator by ID", description = "Deletes a loan originator by its internal ID. Requires DELETE_LOAN_ORIGINATOR permission.") @Operation(summary = "Delete a loan originator by ID", description = "Deletes a loan originator by its internal ID. Requires DELETE_LOAN_ORIGINATOR permission.")
@ApiResponse(responseCode = "200", description = "OK") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.DeleteLoanOriginatorsResponse.class)))
@ApiResponse(responseCode = "403", description = "Originator is mapped to loans and cannot be deleted, or insufficient permissions") @ApiResponse(responseCode = "403", description = "Originator is mapped to loans and cannot be deleted, or insufficient permissions")
@ApiResponse(responseCode = "404", description = "Originator not found") @ApiResponse(responseCode = "404", description = "Originator not found")
public CommandProcessingResult delete(@PathParam("originatorId") @Parameter(description = "originatorId") final Long originatorId) { public CommandProcessingResult delete(@PathParam("originatorId") @Parameter(description = "originatorId") final Long originatorId) {
@@ -160,7 +167,7 @@ public class LoanOriginatorApiResource {
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete a loan originator by external ID", description = "Deletes a loan originator by its external ID. Requires DELETE_LOAN_ORIGINATOR permission.") @Operation(summary = "Delete a loan originator by external ID", description = "Deletes a loan originator by its external ID. Requires DELETE_LOAN_ORIGINATOR permission.")
@ApiResponse(responseCode = "200", description = "OK") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.DeleteLoanOriginatorsResponse.class)))
@ApiResponse(responseCode = "403", description = "Originator is mapped to loans and cannot be deleted, or insufficient permissions") @ApiResponse(responseCode = "403", description = "Originator is mapped to loans and cannot be deleted, or insufficient permissions")
@ApiResponse(responseCode = "404", description = "Originator not found") @ApiResponse(responseCode = "404", description = "Originator not found")
public CommandProcessingResult deleteByExternalId( public CommandProcessingResult deleteByExternalId(
@@ -0,0 +1,104 @@
/**
* 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.portfolio.loanorigination.api;
import io.swagger.v3.oas.annotations.media.Schema;
final class LoanOriginatorApiResourceSwagger {
private LoanOriginatorApiResourceSwagger() {}
@Schema(description = "GetLoanOriginatorsResponse")
public static final class GetLoanOriginatorsResponse {
private GetLoanOriginatorsResponse() {}
@Schema(example = "1")
public Long id;
@Schema(example = "EXT-001")
public String externalId;
@Schema(example = "Main Branch Originator")
public String name;
@Schema(example = "ACTIVE")
public String status;
@Schema(example = "1")
public Long originatorTypeId;
@Schema(example = "1")
public Long channelTypeId;
}
@Schema(description = "PostLoanOriginatorsRequest")
public static final class PostLoanOriginatorsRequest {
private PostLoanOriginatorsRequest() {}
@Schema(example = "EXT-001")
public String externalId;
@Schema(example = "Main Branch Originator", requiredMode = Schema.RequiredMode.REQUIRED)
public String name;
@Schema(example = "ACTIVE")
public String status;
@Schema(example = "1")
public Long originatorTypeId;
@Schema(example = "1")
public Long channelTypeId;
}
@Schema(description = "PostLoanOriginatorsResponse")
public static final class PostLoanOriginatorsResponse {
private PostLoanOriginatorsResponse() {}
@Schema(example = "1")
public Long resourceId;
}
@Schema(description = "PutLoanOriginatorsRequest")
public static final class PutLoanOriginatorsRequest {
private PutLoanOriginatorsRequest() {}
@Schema(example = "Updated Name")
public String name;
@Schema(example = "INACTIVE")
public String status;
@Schema(example = "2")
public Long originatorTypeId;
@Schema(example = "2")
public Long channelTypeId;
}
@Schema(description = "PutLoanOriginatorsResponse")
public static final class PutLoanOriginatorsResponse {
private PutLoanOriginatorsResponse() {}
@Schema(example = "1")
public Long resourceId;
}
@Schema(description = "DeleteLoanOriginatorsResponse")
public static final class DeleteLoanOriginatorsResponse {
private DeleteLoanOriginatorsResponse() {}
@Schema(example = "1")
public Long resourceId;
}
}
@@ -23,6 +23,8 @@ import java.util.Optional;
import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface LoanOriginatorRepository extends JpaRepository<LoanOriginator, Long>, JpaSpecificationExecutor<LoanOriginator> { public interface LoanOriginatorRepository extends JpaRepository<LoanOriginator, Long>, JpaSpecificationExecutor<LoanOriginator> {
@@ -31,4 +33,13 @@ public interface LoanOriginatorRepository extends JpaRepository<LoanOriginator,
boolean existsByExternalId(ExternalId externalId); boolean existsByExternalId(ExternalId externalId);
List<LoanOriginator> findByStatus(LoanOriginatorStatus status); List<LoanOriginator> findByStatus(LoanOriginatorStatus status);
@Query("SELECT lo FROM LoanOriginator lo LEFT JOIN FETCH lo.originatorType LEFT JOIN FETCH lo.channelType")
List<LoanOriginator> findAllWithCodeValues();
@Query("SELECT lo FROM LoanOriginator lo LEFT JOIN FETCH lo.originatorType LEFT JOIN FETCH lo.channelType WHERE lo.id = :id")
Optional<LoanOriginator> findByIdWithCodeValues(@Param("id") Long id);
@Query("SELECT lo FROM LoanOriginator lo LEFT JOIN FETCH lo.originatorType LEFT JOIN FETCH lo.channelType WHERE lo.externalId = :externalId")
Optional<LoanOriginator> findByExternalIdWithCodeValues(@Param("externalId") ExternalId externalId);
} }
@@ -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.portfolio.loanorigination.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanOriginatorCannotBeDeletedException extends AbstractPlatformDomainRuleException {
public LoanOriginatorCannotBeDeletedException(Long id) {
super("error.msg.loan.originator.cannot.be.deleted.mapped.to.loan",
"Loan Originator with id " + id + " cannot be deleted as it is mapped to one or more loans", id);
}
}
@@ -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.portfolio.loanorigination.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanOriginatorDuplicateExternalIdException extends AbstractPlatformDomainRuleException {
public LoanOriginatorDuplicateExternalIdException(String externalId) {
super("error.msg.loan.originator.duplicate.external.id", "Loan Originator with external id '" + externalId + "' already exists",
externalId);
}
}
@@ -0,0 +1,34 @@
/**
* 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.portfolio.loanorigination.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanOriginatorInvalidStatusException extends AbstractPlatformDomainRuleException {
public LoanOriginatorInvalidStatusException(String status) {
super("error.msg.loan.originator.invalid.status",
"Invalid loan originator status: " + status + ". Valid values are: ACTIVE, PENDING, INACTIVE", status);
}
public LoanOriginatorInvalidStatusException(String status, Throwable cause) {
super("error.msg.loan.originator.invalid.status",
"Invalid loan originator status: " + status + ". Valid values are: ACTIVE, PENDING, INACTIVE", status, cause);
}
}
@@ -0,0 +1,33 @@
/**
* 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.portfolio.loanorigination.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class LoanOriginatorNotFoundException extends AbstractPlatformResourceNotFoundException {
public LoanOriginatorNotFoundException(Long id) {
super("error.msg.loan.originator.id.not.found", "Loan Originator with id " + id + " not found", id);
}
public LoanOriginatorNotFoundException(String externalId) {
super("error.msg.loan.originator.external.id.not.found", "Loan Originator with external id " + externalId + " not found",
externalId);
}
}
@@ -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.portfolio.loanorigination.handler;
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.apache.fineract.portfolio.loanorigination.service.LoanOriginatorWritePlatformService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
@Service
@CommandType(entity = "LOAN_ORIGINATOR", action = "CREATE")
@RequiredArgsConstructor
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public class CreateLoanOriginatorCommandHandler implements NewCommandSourceHandler {
private final LoanOriginatorWritePlatformService writePlatformService;
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.create(command);
}
}
@@ -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.portfolio.loanorigination.handler;
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.apache.fineract.portfolio.loanorigination.service.LoanOriginatorWritePlatformService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
@Service
@CommandType(entity = "LOAN_ORIGINATOR", action = "DELETE")
@RequiredArgsConstructor
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public class DeleteLoanOriginatorCommandHandler implements NewCommandSourceHandler {
private final LoanOriginatorWritePlatformService writePlatformService;
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.delete(command.entityId());
}
}
@@ -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.portfolio.loanorigination.handler;
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.apache.fineract.portfolio.loanorigination.service.LoanOriginatorWritePlatformService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@CommandType(entity = "LOAN_ORIGINATOR", action = "UPDATE")
@RequiredArgsConstructor
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public class UpdateLoanOriginatorCommandHandler implements NewCommandSourceHandler {
private final LoanOriginatorWritePlatformService writePlatformService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.update(command.entityId(), command);
}
}
@@ -0,0 +1,40 @@
/**
* 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.portfolio.loanorigination.mapper;
import java.util.List;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@Mapper(config = MapstructMapperConfig.class)
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public interface LoanOriginatorMapper {
@Mapping(target = "originatorTypeId", source = "originatorType.id")
@Mapping(target = "channelTypeId", source = "channelType.id")
@Mapping(target = "externalId", source = "externalId")
@Mapping(target = "status", expression = "java(entity.getStatus().getValue())")
LoanOriginatorData toData(LoanOriginator entity);
List<LoanOriginatorData> toDataList(List<LoanOriginator> entities);
}
@@ -0,0 +1,121 @@
/**
* 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.portfolio.loanorigination.serialization;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.CREATE_REQUEST_PARAMS;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.EXTERNAL_ID_PARAM;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.NAME_PARAM;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.RESOURCE_NAME;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.STATUS_PARAM;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.UPDATE_REQUEST_PARAMS;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorStatus;
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorInvalidStatusException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public class LoanOriginatorDataValidator {
private final FromJsonHelper fromApiJsonHelper;
public void validateForCreate(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CREATE_REQUEST_PARAMS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(RESOURCE_NAME);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final String externalId = this.fromApiJsonHelper.extractStringNamed(EXTERNAL_ID_PARAM, element);
baseDataValidator.reset().parameter(EXTERNAL_ID_PARAM).value(externalId).notBlank().notExceedingLengthOf(100);
final String name = this.fromApiJsonHelper.extractStringNamed(NAME_PARAM, element);
baseDataValidator.reset().parameter(NAME_PARAM).value(name).ignoreIfNull().notExceedingLengthOf(255);
if (this.fromApiJsonHelper.parameterExists(STATUS_PARAM, element)) {
final String status = this.fromApiJsonHelper.extractStringNamed(STATUS_PARAM, element);
baseDataValidator.reset().parameter(STATUS_PARAM).value(status).notBlank();
validateStatus(status);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForUpdate(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, UPDATE_REQUEST_PARAMS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(RESOURCE_NAME);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final String name = this.fromApiJsonHelper.extractStringNamed(NAME_PARAM, element);
baseDataValidator.reset().parameter(NAME_PARAM).value(name).ignoreIfNull().notExceedingLengthOf(255);
if (this.fromApiJsonHelper.parameterExists(STATUS_PARAM, element)) {
final String status = this.fromApiJsonHelper.extractStringNamed(STATUS_PARAM, element);
baseDataValidator.reset().parameter(STATUS_PARAM).value(status).notBlank();
validateStatus(status);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void validateStatus(final String status) {
if (status != null) {
try {
LoanOriginatorStatus.fromString(status);
} catch (IllegalArgumentException e) {
throw new LoanOriginatorInvalidStatusException(status, e);
}
}
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
}
@@ -19,33 +19,50 @@
package org.apache.fineract.portfolio.loanorigination.service; package org.apache.fineract.portfolio.loanorigination.service;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData; import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorRepository;
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorNotFoundException;
import org.apache.fineract.portfolio.loanorigination.mapper.LoanOriginatorMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true") @ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public class LoanOriginatorReadPlatformServiceImpl implements LoanOriginatorReadPlatformService { public class LoanOriginatorReadPlatformServiceImpl implements LoanOriginatorReadPlatformService {
private static final String NOT_IMPLEMENTED_MESSAGE = "Not implemented yet"; private final LoanOriginatorRepository loanOriginatorRepository;
private final LoanOriginatorMapper loanOriginatorMapper;
@Override @Override
public List<LoanOriginatorData> retrieveAll() { public List<LoanOriginatorData> retrieveAll() {
throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE); final List<LoanOriginator> originators = this.loanOriginatorRepository.findAllWithCodeValues();
return this.loanOriginatorMapper.toDataList(originators);
} }
@Override @Override
public LoanOriginatorData retrieveById(Long id) { public LoanOriginatorData retrieveById(final Long id) {
throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE); final LoanOriginator originator = this.loanOriginatorRepository.findByIdWithCodeValues(id)
.orElseThrow(() -> new LoanOriginatorNotFoundException(id));
return this.loanOriginatorMapper.toData(originator);
} }
@Override @Override
public LoanOriginatorData retrieveByExternalId(String externalId) { public LoanOriginatorData retrieveByExternalId(final String externalId) {
throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE); final LoanOriginator originator = this.loanOriginatorRepository.findByExternalIdWithCodeValues(new ExternalId(externalId))
.orElseThrow(() -> new LoanOriginatorNotFoundException(externalId));
return this.loanOriginatorMapper.toData(originator);
} }
@Override @Override
public Long resolveIdByExternalId(String externalId) { public Long resolveIdByExternalId(final String externalId) {
throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE); final LoanOriginator originator = this.loanOriginatorRepository.findByExternalId(new ExternalId(externalId))
.orElseThrow(() -> new LoanOriginatorNotFoundException(externalId));
return originator.getId();
} }
} }
@@ -0,0 +1,154 @@
/**
* 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.portfolio.loanorigination.service;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.CHANNEL_TYPE_CODE_NAME;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.CHANNEL_TYPE_ID_PARAM;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.EXTERNAL_ID_PARAM;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.NAME_PARAM;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.ORIGINATOR_TYPE_CODE_NAME;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.ORIGINATOR_TYPE_ID_PARAM;
import static org.apache.fineract.portfolio.loanorigination.api.LoanOriginatorApiConstants.STATUS_PARAM;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorRepository;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorStatus;
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorCannotBeDeletedException;
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorDuplicateExternalIdException;
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorNotFoundException;
import org.apache.fineract.portfolio.loanorigination.serialization.LoanOriginatorDataValidator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public class LoanOriginatorWritePlatformServiceImpl implements LoanOriginatorWritePlatformService {
private final LoanOriginatorRepository loanOriginatorRepository;
private final LoanOriginatorMappingRepository loanOriginatorMappingRepository;
private final LoanOriginatorDataValidator loanOriginatorDataValidator;
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
@Override
public CommandProcessingResult create(final JsonCommand command) {
this.loanOriginatorDataValidator.validateForCreate(command.json());
final String externalIdValue = command.stringValueOfParameterNamed(EXTERNAL_ID_PARAM);
final ExternalId externalId = new ExternalId(externalIdValue);
if (this.loanOriginatorRepository.existsByExternalId(externalId)) {
throw new LoanOriginatorDuplicateExternalIdException(externalIdValue);
}
final String name = command.stringValueOfParameterNamed(NAME_PARAM);
final String statusValue = command.stringValueOfParameterNamed(STATUS_PARAM);
final LoanOriginatorStatus status = (statusValue != null && !statusValue.isEmpty()) ? LoanOriginatorStatus.fromString(statusValue)
: LoanOriginatorStatus.ACTIVE;
final CodeValue originatorType = resolveCodeValue(command, ORIGINATOR_TYPE_ID_PARAM, ORIGINATOR_TYPE_CODE_NAME);
final CodeValue channelType = resolveCodeValue(command, CHANNEL_TYPE_ID_PARAM, CHANNEL_TYPE_CODE_NAME);
final LoanOriginator originator = LoanOriginator.create(externalId, name, status, originatorType, channelType);
this.loanOriginatorRepository.saveAndFlush(originator);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(originator.getId())
.withEntityExternalId(externalId).build();
}
@Override
public CommandProcessingResult update(final Long id, final JsonCommand command) {
this.loanOriginatorDataValidator.validateForUpdate(command.json());
final LoanOriginator originator = this.loanOriginatorRepository.findById(id)
.orElseThrow(() -> new LoanOriginatorNotFoundException(id));
final Map<String, Object> changes = new LinkedHashMap<>();
if (command.isChangeInStringParameterNamed(NAME_PARAM, originator.getName())) {
final String newName = command.stringValueOfParameterNamed(NAME_PARAM);
originator.setName(newName);
changes.put(NAME_PARAM, newName);
}
if (command.isChangeInStringParameterNamed(STATUS_PARAM, originator.getStatus().getValue())) {
final String newStatusValue = command.stringValueOfParameterNamed(STATUS_PARAM);
final LoanOriginatorStatus newStatus = LoanOriginatorStatus.fromString(newStatusValue);
originator.setStatus(newStatus);
changes.put(STATUS_PARAM, newStatusValue);
}
final Long currentOriginatorTypeId = originator.getOriginatorType() != null ? originator.getOriginatorType().getId() : null;
if (command.isChangeInLongParameterNamed(ORIGINATOR_TYPE_ID_PARAM, currentOriginatorTypeId)) {
final CodeValue newOriginatorType = resolveCodeValue(command, ORIGINATOR_TYPE_ID_PARAM, ORIGINATOR_TYPE_CODE_NAME);
originator.setOriginatorType(newOriginatorType);
changes.put(ORIGINATOR_TYPE_ID_PARAM, newOriginatorType != null ? newOriginatorType.getId() : null);
}
final Long currentChannelTypeId = originator.getChannelType() != null ? originator.getChannelType().getId() : null;
if (command.isChangeInLongParameterNamed(CHANNEL_TYPE_ID_PARAM, currentChannelTypeId)) {
final CodeValue newChannelType = resolveCodeValue(command, CHANNEL_TYPE_ID_PARAM, CHANNEL_TYPE_CODE_NAME);
originator.setChannelType(newChannelType);
changes.put(CHANNEL_TYPE_ID_PARAM, newChannelType != null ? newChannelType.getId() : null);
}
if (!changes.isEmpty()) {
this.loanOriginatorRepository.saveAndFlush(originator);
}
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(originator.getId())
.withEntityExternalId(originator.getExternalId()).with(changes).build();
}
@Override
public CommandProcessingResult delete(final Long id) {
final LoanOriginator originator = this.loanOriginatorRepository.findById(id)
.orElseThrow(() -> new LoanOriginatorNotFoundException(id));
if (this.loanOriginatorMappingRepository.existsByOriginatorId(id)) {
throw new LoanOriginatorCannotBeDeletedException(id);
}
final ExternalId externalId = originator.getExternalId();
this.loanOriginatorRepository.delete(originator);
return new CommandProcessingResultBuilder().withEntityId(id).withEntityExternalId(externalId).build();
}
private CodeValue resolveCodeValue(final JsonCommand command, final String paramName, final String codeName) {
final Long codeValueId = command.longValueOfParameterNamed(paramName);
if (codeValueId == null) {
return null;
}
return this.codeValueRepositoryWrapper.findOneByCodeNameAndIdWithNotFoundDetection(codeName, codeValueId);
}
}
@@ -23,4 +23,5 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd"> xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<include relativeToChangelogFile="true" file="parts/0001_initial_schema.xml"/> <include relativeToChangelogFile="true" file="parts/0001_initial_schema.xml"/>
<include relativeToChangelogFile="true" file="parts/0002_permissions.xml"/>
</databaseChangeLog> </databaseChangeLog>
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet id="loan-origination-perm-001" author="fineract">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(1) FROM m_permission WHERE code = 'CREATE_LOAN_ORIGINATOR'</sqlCheck>
</preConditions>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="CREATE_LOAN_ORIGINATOR"/>
<column name="entity_name" value="LOAN_ORIGINATOR"/>
<column name="action_name" value="CREATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
<changeSet id="loan-origination-perm-002" author="fineract">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(1) FROM m_permission WHERE code = 'READ_LOAN_ORIGINATOR'</sqlCheck>
</preConditions>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="READ_LOAN_ORIGINATOR"/>
<column name="entity_name" value="LOAN_ORIGINATOR"/>
<column name="action_name" value="READ"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
<changeSet id="loan-origination-perm-003" author="fineract">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(1) FROM m_permission WHERE code = 'UPDATE_LOAN_ORIGINATOR'</sqlCheck>
</preConditions>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="UPDATE_LOAN_ORIGINATOR"/>
<column name="entity_name" value="LOAN_ORIGINATOR"/>
<column name="action_name" value="UPDATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
<changeSet id="loan-origination-perm-004" author="fineract">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(1) FROM m_permission WHERE code = 'DELETE_LOAN_ORIGINATOR'</sqlCheck>
</preConditions>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="DELETE_LOAN_ORIGINATOR"/>
<column name="entity_name" value="LOAN_ORIGINATOR"/>
<column name="action_name" value="DELETE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
</databaseChangeLog>
@@ -212,7 +212,7 @@ fineract.sampling.resetPeriodSec=${FINERACT_SAMPLING_RESET_PERIOD_IN_SEC:60}
#Modules #Modules
fineract.module.self-service.enabled=${FINERACT_MODULE_SELF_SERVICE_ENABLED:false} fineract.module.self-service.enabled=${FINERACT_MODULE_SELF_SERVICE_ENABLED:false}
fineract.module.investor.enabled=${FINERACT_MODULE_INVESTOR_ENABLED:true} fineract.module.investor.enabled=${FINERACT_MODULE_INVESTOR_ENABLED:true}
fineract.module.loan-origination.enabled=${FINERACT_MODULE_LOAN_ORIGINATION_ENABLED:false} fineract.module.loan-origination.enabled=${FINERACT_MODULE_LOAN_ORIGINATION_ENABLED:true}
fineract.insecure-http-client=${FINERACT_INSECURE_HTTP_CLIENT:true} fineract.insecure-http-client=${FINERACT_INSECURE_HTTP_CLIENT:true}
fineract.client-connect-timeout=${FINERACT_CLIENT_CONNECT_TIMEOUT:30} fineract.client-connect-timeout=${FINERACT_CLIENT_CONNECT_TIMEOUT:30}
@@ -125,8 +125,7 @@ fineract.sampling.sampledClasses=
fineract.module.investor.enabled=true fineract.module.investor.enabled=true
fineract.module.self-service.enabled=true fineract.module.self-service.enabled=true
# TODO: activate this module when the implementation is ready fineract.module.loan-origination.enabled=true
fineract.module.loan-origination.enabled=false
# sql validation # sql validation
@@ -0,0 +1,280 @@
/**
* 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.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.HashMap;
import java.util.List;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.loans.LoanOriginatorHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
@Order(1)
public class LoanOriginatorApiTest {
private RequestSpecification requestSpec;
private ResponseSpecification responseSpec;
private ResponseSpecification responseSpec400;
private ResponseSpecification responseSpec403;
private ResponseSpecification responseSpec404;
@BeforeEach
public void setup() {
Utils.initializeRESTAssured();
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
this.responseSpec400 = new ResponseSpecBuilder().expectStatusCode(400).build();
this.responseSpec403 = new ResponseSpecBuilder().expectStatusCode(403).build();
this.responseSpec404 = new ResponseSpecBuilder().expectStatusCode(404).build();
}
// ==================== CRUD LIFECYCLE TESTS ====================
@Test
public void testCreateOriginatorWithMinimalData() {
final String externalId = LoanOriginatorHelper.generateUniqueExternalId();
// Create with only externalId
final Integer originatorId = LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
assertNotNull(originatorId, "Originator ID should not be null");
// Verify created data
final HashMap<String, Object> originator = LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec, originatorId);
assertEquals(externalId, originator.get("externalId"));
assertEquals("ACTIVE", originator.get("status"), "Default status should be ACTIVE");
// Cleanup
LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, originatorId);
}
@Test
public void testCreateOriginatorWithAllFields() {
final String externalId = LoanOriginatorHelper.generateUniqueExternalId();
final String name = "Test Originator";
final String status = "PENDING";
final Integer originatorId = LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId, name, status, null, null);
assertNotNull(originatorId);
final HashMap<String, Object> originator = LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec, originatorId);
assertEquals(externalId, originator.get("externalId"));
assertEquals(name, originator.get("name"));
assertEquals(status, originator.get("status"));
// Cleanup
LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, originatorId);
}
@Test
public void testRetrieveOriginatorById() {
final String externalId = LoanOriginatorHelper.generateUniqueExternalId();
final Integer originatorId = LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
final HashMap<String, Object> originator = LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec, originatorId);
assertNotNull(originator);
assertEquals(originatorId, originator.get("id"));
assertEquals(externalId, originator.get("externalId"));
// Cleanup
LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, originatorId);
}
@Test
public void testRetrieveOriginatorByExternalId() {
final String externalId = LoanOriginatorHelper.generateUniqueExternalId();
final Integer originatorId = LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
final HashMap<String, Object> originator = LoanOriginatorHelper.getOriginatorByExternalId(requestSpec, responseSpec, externalId);
assertNotNull(originator);
assertEquals(originatorId, originator.get("id"));
assertEquals(externalId, originator.get("externalId"));
// Cleanup
LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, originatorId);
}
@Test
public void testRetrieveAllOriginators() {
final String externalId1 = LoanOriginatorHelper.generateUniqueExternalId();
final String externalId2 = LoanOriginatorHelper.generateUniqueExternalId();
final Integer originatorId1 = LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId1);
final Integer originatorId2 = LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId2);
final List<HashMap<String, Object>> originators = LoanOriginatorHelper.getAllOriginators(requestSpec, responseSpec);
assertNotNull(originators);
assertTrue(originators.size() >= 2, "Should have at least 2 originators");
// Cleanup
LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, originatorId1);
LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, originatorId2);
}
@Test
public void testUpdateOriginatorPartially() {
final String externalId = LoanOriginatorHelper.generateUniqueExternalId();
final Integer originatorId = LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId, "Original Name", "ACTIVE",
null, null);
// Update only the name
final HashMap<String, Object> updateResult = LoanOriginatorHelper.updateOriginator(requestSpec, responseSpec, originatorId,
"Updated Name", null, null, null);
assertNotNull(updateResult);
assertTrue(updateResult.containsKey("changes"), "Response should contain changes");
// Verify update
final HashMap<String, Object> originator = LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec, originatorId);
assertEquals("Updated Name", originator.get("name"));
assertEquals("ACTIVE", originator.get("status"), "Status should remain unchanged");
// Cleanup
LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, originatorId);
}
@Test
public void testUpdateOriginatorByExternalId() {
final String externalId = LoanOriginatorHelper.generateUniqueExternalId();
LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId, "Original Name", "ACTIVE", null, null);
// Update by external ID
final HashMap<String, Object> updateResult = LoanOriginatorHelper.updateOriginatorByExternalId(requestSpec, responseSpec,
externalId, "Updated via ExternalId", null, null, null);
assertNotNull(updateResult);
// Verify update
final HashMap<String, Object> originator = LoanOriginatorHelper.getOriginatorByExternalId(requestSpec, responseSpec, externalId);
assertEquals("Updated via ExternalId", originator.get("name"));
// Cleanup
LoanOriginatorHelper.deleteOriginatorByExternalId(requestSpec, responseSpec, externalId);
}
@Test
public void testDeleteOriginator() {
final String externalId = LoanOriginatorHelper.generateUniqueExternalId();
final Integer originatorId = LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
// Delete
final Integer deletedId = LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, originatorId);
assertEquals(originatorId, deletedId);
// Verify deleted - should return 404
LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec404, originatorId);
}
@Test
public void testDeleteOriginatorByExternalId() {
final String externalId = LoanOriginatorHelper.generateUniqueExternalId();
final Integer originatorId = LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
// Delete by external ID
final Integer deletedId = LoanOriginatorHelper.deleteOriginatorByExternalId(requestSpec, responseSpec, externalId);
assertEquals(originatorId, deletedId);
// Verify deleted - should return 404
LoanOriginatorHelper.getOriginatorByExternalId(requestSpec, responseSpec404, externalId);
}
// ==================== VALIDATION ERROR TESTS ====================
@Test
public void testCreateOriginatorWithMissingExternalId() {
final String invalidJson = "{ \"name\": \"Test\" }";
LoanOriginatorHelper.createOriginatorExpectingError(requestSpec, responseSpec400, invalidJson);
}
@Test
public void testCreateOriginatorWithDuplicateExternalId() {
final String externalId = LoanOriginatorHelper.generateUniqueExternalId();
// Create first originator
final Integer originatorId = LoanOriginatorHelper.createOriginator(requestSpec, responseSpec, externalId);
// Attempt to create duplicate - should return 403
final String duplicateJson = "{ \"externalId\": \"" + externalId + "\" }";
LoanOriginatorHelper.createOriginatorExpectingError(requestSpec, responseSpec403, duplicateJson);
// Cleanup
LoanOriginatorHelper.deleteOriginator(requestSpec, responseSpec, originatorId);
}
@Test
public void testCreateOriginatorWithInvalidStatus() {
final String externalId = LoanOriginatorHelper.generateUniqueExternalId();
final String invalidJson = "{ \"externalId\": \"" + externalId + "\", \"status\": \"INVALID\" }";
LoanOriginatorHelper.createOriginatorExpectingError(requestSpec, responseSpec403, invalidJson);
}
@Test
public void testGetOriginatorByNonExistentId() {
LoanOriginatorHelper.getOriginatorById(requestSpec, responseSpec404, 999999);
}
@Test
public void testGetOriginatorByNonExistentExternalId() {
LoanOriginatorHelper.getOriginatorByExternalId(requestSpec, responseSpec404, "NON-EXISTENT-EXTERNAL-ID");
}
@Test
public void testUpdateNonExistentOriginator() {
final String json = "{ \"name\": \"Updated\" }";
Utils.performServerPut(requestSpec, responseSpec404, "/fineract-provider/api/v1/loan-originators/999999?" + Utils.TENANT_IDENTIFIER,
json, "");
}
@Test
public void testDeleteNonExistentOriginator() {
Utils.performServerDelete(requestSpec, responseSpec404,
"/fineract-provider/api/v1/loan-originators/999999?" + Utils.TENANT_IDENTIFIER, "");
}
// ==================== CODE VALUE VALIDATION TESTS ====================
@Test
public void testCreateOriginatorWithInvalidCodeValueId() {
final String externalId = LoanOriginatorHelper.generateUniqueExternalId();
// Use an ID that doesn't exist
final String json = "{ \"externalId\": \"" + externalId + "\", \"originatorTypeId\": 999999 }";
LoanOriginatorHelper.createOriginatorExpectingError(requestSpec, responseSpec404, json);
}
}
@@ -0,0 +1,108 @@
/**
* 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.integrationtests.client.feign.helpers;
import static org.apache.fineract.client.feign.util.FeignCalls.fail;
import static org.apache.fineract.client.feign.util.FeignCalls.ok;
import java.util.List;
import java.util.UUID;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
import org.apache.fineract.client.models.GetLoanOriginatorsResponse;
import org.apache.fineract.client.models.PostLoanOriginatorsRequest;
import org.apache.fineract.client.models.PostLoanOriginatorsResponse;
import org.apache.fineract.client.models.PutLoanOriginatorsRequest;
import org.apache.fineract.client.models.PutLoanOriginatorsResponse;
public class FeignLoanOriginatorHelper {
private final FineractFeignClient fineractClient;
public FeignLoanOriginatorHelper(FineractFeignClient fineractClient) {
this.fineractClient = fineractClient;
}
public Long createOriginator(String externalId) {
return createOriginator(new PostLoanOriginatorsRequest().externalId(externalId).name(externalId));
}
public Long createOriginator(String externalId, String name, String status) {
return createOriginator(new PostLoanOriginatorsRequest().externalId(externalId).name(name).status(status));
}
public Long createOriginator(PostLoanOriginatorsRequest request) {
PostLoanOriginatorsResponse response = ok(() -> fineractClient.loanOriginators().create11(request));
return response.getResourceId();
}
public CallFailedRuntimeException createOriginatorExpectingError(PostLoanOriginatorsRequest request) {
return fail(() -> fineractClient.loanOriginators().create11(request));
}
public List<GetLoanOriginatorsResponse> getAllOriginators() {
return ok(() -> fineractClient.loanOriginators().retrieveAll28());
}
public GetLoanOriginatorsResponse getOriginatorById(Long originatorId) {
return ok(() -> fineractClient.loanOriginators().retrieveOne18(originatorId));
}
public CallFailedRuntimeException getOriginatorByIdExpectingError(Long originatorId) {
return fail(() -> fineractClient.loanOriginators().retrieveOne18(originatorId));
}
public GetLoanOriginatorsResponse getOriginatorByExternalId(String externalId) {
return ok(() -> fineractClient.loanOriginators().retrieveByExternalId(externalId));
}
public CallFailedRuntimeException getOriginatorByExternalIdExpectingError(String externalId) {
return fail(() -> fineractClient.loanOriginators().retrieveByExternalId(externalId));
}
public PutLoanOriginatorsResponse updateOriginator(Long originatorId, PutLoanOriginatorsRequest request) {
return ok(() -> fineractClient.loanOriginators().update16(originatorId, request));
}
public PutLoanOriginatorsResponse updateOriginatorByExternalId(String externalId, PutLoanOriginatorsRequest request) {
return ok(() -> fineractClient.loanOriginators().updateByExternalId(externalId, request));
}
public CallFailedRuntimeException updateOriginatorExpectingError(Long originatorId, PutLoanOriginatorsRequest request) {
return fail(() -> fineractClient.loanOriginators().update16(originatorId, request));
}
public Long deleteOriginator(Long originatorId) {
var response = ok(() -> fineractClient.loanOriginators().delete14(originatorId));
return response.getResourceId();
}
public Long deleteOriginatorByExternalId(String externalId) {
var response = ok(() -> fineractClient.loanOriginators().deleteByExternalId(externalId));
return response.getResourceId();
}
public CallFailedRuntimeException deleteOriginatorExpectingError(Long originatorId) {
return fail(() -> fineractClient.loanOriginators().delete14(originatorId));
}
public static String generateUniqueExternalId() {
return "EXT-" + UUID.randomUUID().toString().substring(0, 8);
}
}
@@ -0,0 +1,248 @@
/**
* 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.integrationtests.client.feign.tests;
import java.util.List;
import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
import org.apache.fineract.client.models.GetLoanOriginatorsResponse;
import org.apache.fineract.client.models.PostLoanOriginatorsRequest;
import org.apache.fineract.client.models.PutLoanOriginatorsRequest;
import org.apache.fineract.client.models.PutLoanOriginatorsResponse;
import org.apache.fineract.integrationtests.client.FeignIntegrationTest;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignLoanOriginatorHelper;
import org.apache.fineract.integrationtests.common.FineractFeignClientHelper;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
@Order(1)
public class FeignLoanOriginatorApiTest extends FeignIntegrationTest {
private static FeignLoanOriginatorHelper originatorHelper;
@BeforeAll
public static void setup() {
originatorHelper = new FeignLoanOriginatorHelper(FineractFeignClientHelper.getFineractFeignClient());
}
@Test
public void testCreateOriginatorWithMinimalData() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(externalId);
assertThat(originatorId).isNotNull();
final GetLoanOriginatorsResponse originator = originatorHelper.getOriginatorById(originatorId);
assertThat(originator.getExternalId()).isEqualTo(externalId);
assertThat(originator.getStatus()).isEqualTo("ACTIVE");
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testCreateOriginatorWithAllFields() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final String name = "Test Originator";
final String status = "PENDING";
final Long originatorId = originatorHelper.createOriginator(externalId, name, status);
assertThat(originatorId).isNotNull();
final GetLoanOriginatorsResponse originator = originatorHelper.getOriginatorById(originatorId);
assertThat(originator.getExternalId()).isEqualTo(externalId);
assertThat(originator.getName()).isEqualTo(name);
assertThat(originator.getStatus()).isEqualTo(status);
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testRetrieveOriginatorById() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(externalId);
final GetLoanOriginatorsResponse originator = originatorHelper.getOriginatorById(originatorId);
assertThat(originator).isNotNull();
assertThat(originator.getId()).isEqualTo(originatorId);
assertThat(originator.getExternalId()).isEqualTo(externalId);
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testRetrieveOriginatorByExternalId() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(externalId);
final GetLoanOriginatorsResponse originator = originatorHelper.getOriginatorByExternalId(externalId);
assertThat(originator).isNotNull();
assertThat(originator.getId()).isEqualTo(originatorId);
assertThat(originator.getExternalId()).isEqualTo(externalId);
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testRetrieveAllOriginators() {
final String externalId1 = FeignLoanOriginatorHelper.generateUniqueExternalId();
final String externalId2 = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId1 = originatorHelper.createOriginator(externalId1);
final Long originatorId2 = originatorHelper.createOriginator(externalId2);
final List<GetLoanOriginatorsResponse> originators = originatorHelper.getAllOriginators();
assertThat(originators).isNotNull();
assertThat(originators).hasSizeGreaterThanOrEqualTo(2);
originatorHelper.deleteOriginator(originatorId1);
originatorHelper.deleteOriginator(originatorId2);
}
@Test
public void testUpdateOriginatorPartially() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(externalId, "Original Name", "ACTIVE");
final PutLoanOriginatorsResponse updateResult = originatorHelper.updateOriginator(originatorId,
new PutLoanOriginatorsRequest().name("Updated Name"));
assertThat(updateResult).isNotNull();
final GetLoanOriginatorsResponse originator = originatorHelper.getOriginatorById(originatorId);
assertThat(originator.getName()).isEqualTo("Updated Name");
assertThat(originator.getStatus()).isEqualTo("ACTIVE");
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testUpdateOriginatorByExternalId() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
originatorHelper.createOriginator(externalId, "Original Name", "ACTIVE");
final PutLoanOriginatorsResponse updateResult = originatorHelper.updateOriginatorByExternalId(externalId,
new PutLoanOriginatorsRequest().name("Updated via ExternalId"));
assertThat(updateResult).isNotNull();
final GetLoanOriginatorsResponse originator = originatorHelper.getOriginatorByExternalId(externalId);
assertThat(originator.getName()).isEqualTo("Updated via ExternalId");
originatorHelper.deleteOriginatorByExternalId(externalId);
}
@Test
public void testDeleteOriginator() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(externalId);
final Long deletedId = originatorHelper.deleteOriginator(originatorId);
assertThat(deletedId).isEqualTo(originatorId);
final CallFailedRuntimeException exception = originatorHelper.getOriginatorByIdExpectingError(originatorId);
assertThat(exception.getStatus()).isEqualTo(404);
}
@Test
public void testDeleteOriginatorByExternalId() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(externalId);
final Long deletedId = originatorHelper.deleteOriginatorByExternalId(externalId);
assertThat(deletedId).isEqualTo(originatorId);
final CallFailedRuntimeException exception = originatorHelper.getOriginatorByExternalIdExpectingError(externalId);
assertThat(exception.getStatus()).isEqualTo(404);
}
@Test
public void testCreateOriginatorWithOptionalName() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final PostLoanOriginatorsRequest request = new PostLoanOriginatorsRequest().externalId(externalId);
final Long originatorId = originatorHelper.createOriginator(request);
assertThat(originatorId).isNotNull();
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testCreateOriginatorWithDuplicateExternalId() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(externalId);
final CallFailedRuntimeException exception = originatorHelper
.createOriginatorExpectingError(new PostLoanOriginatorsRequest().externalId(externalId).name("Duplicate"));
assertThat(exception.getStatus()).isEqualTo(403);
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testCreateOriginatorWithInvalidStatus() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final PostLoanOriginatorsRequest request = new PostLoanOriginatorsRequest().externalId(externalId).name("Test").status("INVALID");
final CallFailedRuntimeException exception = originatorHelper.createOriginatorExpectingError(request);
assertThat(exception.getStatus()).isEqualTo(403);
}
@Test
public void testGetOriginatorByNonExistentId() {
final CallFailedRuntimeException exception = originatorHelper.getOriginatorByIdExpectingError(999999L);
assertThat(exception.getStatus()).isEqualTo(404);
}
@Test
public void testGetOriginatorByNonExistentExternalId() {
final CallFailedRuntimeException exception = originatorHelper.getOriginatorByExternalIdExpectingError("NON-EXISTENT-EXTERNAL-ID");
assertThat(exception.getStatus()).isEqualTo(404);
}
@Test
public void testUpdateNonExistentOriginator() {
final CallFailedRuntimeException exception = originatorHelper.updateOriginatorExpectingError(999999L,
new PutLoanOriginatorsRequest().name("Updated"));
assertThat(exception.getStatus()).isEqualTo(404);
}
@Test
public void testDeleteNonExistentOriginator() {
final CallFailedRuntimeException exception = originatorHelper.deleteOriginatorExpectingError(999999L);
assertThat(exception.getStatus()).isEqualTo(404);
}
@Test
public void testCreateOriginatorWithInvalidCodeValueId() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final PostLoanOriginatorsRequest request = new PostLoanOriginatorsRequest().externalId(externalId).name("Test")
.originatorTypeId(999999L);
final CallFailedRuntimeException exception = originatorHelper.createOriginatorExpectingError(request);
assertThat(exception.getStatus()).isEqualTo(404);
}
}
@@ -0,0 +1,166 @@
/**
* 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.integrationtests.common.loans;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import org.apache.fineract.integrationtests.common.Utils;
public final class LoanOriginatorHelper {
private static final String LOAN_ORIGINATOR_URL = "/fineract-provider/api/v1/loan-originators";
private LoanOriginatorHelper() {}
// ========== CREATE ==========
public static Integer createOriginator(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String externalId) {
return createOriginator(requestSpec, responseSpec, externalId, null, null, null, null);
}
public static Integer createOriginator(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String externalId, final String name, final String status, final Long originatorTypeId, final Long channelTypeId) {
final String json = buildCreateJson(externalId, name, status, originatorTypeId, channelTypeId);
return Utils.performServerPost(requestSpec, responseSpec, LOAN_ORIGINATOR_URL + "?" + Utils.TENANT_IDENTIFIER, json, "resourceId");
}
public static HashMap<String, Object> createOriginatorWithFullResponse(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final String externalId) {
final String json = buildCreateJson(externalId, null, null, null, null);
return Utils.performServerPost(requestSpec, responseSpec, LOAN_ORIGINATOR_URL + "?" + Utils.TENANT_IDENTIFIER, json, "");
}
public static List<HashMap<String, Object>> createOriginatorExpectingError(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final String json) {
return Utils.performServerPost(requestSpec, responseSpec, LOAN_ORIGINATOR_URL + "?" + Utils.TENANT_IDENTIFIER, json, "errors");
}
// ========== READ ==========
public static HashMap<String, Object> getOriginatorById(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final Integer originatorId) {
return Utils.performServerGet(requestSpec, responseSpec, LOAN_ORIGINATOR_URL + "/" + originatorId + "?" + Utils.TENANT_IDENTIFIER,
"");
}
public static HashMap<String, Object> getOriginatorByExternalId(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final String externalId) {
return Utils.performServerGet(requestSpec, responseSpec,
LOAN_ORIGINATOR_URL + "/external-id/" + externalId + "?" + Utils.TENANT_IDENTIFIER, "");
}
public static List<HashMap<String, Object>> getAllOriginators(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec) {
return Utils.performServerGet(requestSpec, responseSpec, LOAN_ORIGINATOR_URL + "?" + Utils.TENANT_IDENTIFIER, "");
}
// ========== UPDATE ==========
public static HashMap<String, Object> updateOriginator(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer originatorId, final String name, final String status, final Long originatorTypeId, final Long channelTypeId) {
final String json = buildUpdateJson(name, status, originatorTypeId, channelTypeId);
return Utils.performServerPut(requestSpec, responseSpec, LOAN_ORIGINATOR_URL + "/" + originatorId + "?" + Utils.TENANT_IDENTIFIER,
json, "");
}
public static HashMap<String, Object> updateOriginatorByExternalId(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final String externalId, final String name, final String status,
final Long originatorTypeId, final Long channelTypeId) {
final String json = buildUpdateJson(name, status, originatorTypeId, channelTypeId);
return Utils.performServerPut(requestSpec, responseSpec,
LOAN_ORIGINATOR_URL + "/external-id/" + externalId + "?" + Utils.TENANT_IDENTIFIER, json, "");
}
// ========== DELETE ==========
public static Integer deleteOriginator(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer originatorId) {
return Utils.performServerDelete(requestSpec, responseSpec,
LOAN_ORIGINATOR_URL + "/" + originatorId + "?" + Utils.TENANT_IDENTIFIER, "resourceId");
}
public static Integer deleteOriginatorByExternalId(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String externalId) {
return Utils.performServerDelete(requestSpec, responseSpec,
LOAN_ORIGINATOR_URL + "/external-id/" + externalId + "?" + Utils.TENANT_IDENTIFIER, "resourceId");
}
// ========== JSON BUILDERS ==========
private static String buildCreateJson(final String externalId, final String name, final String status, final Long originatorTypeId,
final Long channelTypeId) {
final StringBuilder json = new StringBuilder("{");
json.append("\"externalId\": \"").append(externalId).append("\"");
if (name != null) {
json.append(", \"name\": \"").append(name).append("\"");
}
if (status != null) {
json.append(", \"status\": \"").append(status).append("\"");
}
if (originatorTypeId != null) {
json.append(", \"originatorTypeId\": ").append(originatorTypeId);
}
if (channelTypeId != null) {
json.append(", \"channelTypeId\": ").append(channelTypeId);
}
json.append("}");
return json.toString();
}
private static String buildUpdateJson(final String name, final String status, final Long originatorTypeId, final Long channelTypeId) {
final StringBuilder json = new StringBuilder("{");
boolean first = true;
if (name != null) {
json.append("\"name\": \"").append(name).append("\"");
first = false;
}
if (status != null) {
if (!first) {
json.append(", ");
}
json.append("\"status\": \"").append(status).append("\"");
first = false;
}
if (originatorTypeId != null) {
if (!first) {
json.append(", ");
}
json.append("\"originatorTypeId\": ").append(originatorTypeId);
first = false;
}
if (channelTypeId != null) {
if (!first) {
json.append(", ");
}
json.append("\"channelTypeId\": ").append(channelTypeId);
}
json.append("}");
return json.toString();
}
// ========== UTILITIES ==========
public static String generateUniqueExternalId() {
return "EXT-" + UUID.randomUUID().toString().substring(0, 8);
}
}