-
Notifications
You must be signed in to change notification settings - Fork 2.2k
FINERACT-2418: add origination-api-skeleton #5350
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
adamsaghy
merged 2 commits into
apache:develop
from
openMF:FINERACT-2418/loan-origination-api-skeleton
Jan 21, 2026
+623
−23
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
fineract-avro-schemas/src/main/avro/loan/v1/OriginatorDetailsV1.avsc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| { | ||
| "name": "OriginatorDetailsV1", | ||
| "namespace": "org.apache.fineract.avro.loan.v1", | ||
| "doc": "Loan originator details for revenue sharing and reporting", | ||
| "type": "record", | ||
| "fields": [ | ||
| { | ||
| "default": null, | ||
| "name": "id", | ||
| "doc": "Internal originator ID", | ||
| "type": [ | ||
| "null", | ||
| "long" | ||
| ] | ||
| }, | ||
| { | ||
| "default": null, | ||
| "name": "externalId", | ||
| "doc": "Unique external identifier (Revenue Share ID)", | ||
| "type": [ | ||
| "null", | ||
| "string" | ||
| ] | ||
| }, | ||
| { | ||
| "default": null, | ||
| "name": "name", | ||
| "doc": "Originator name", | ||
| "type": [ | ||
| "null", | ||
| "string" | ||
| ] | ||
| }, | ||
| { | ||
| "default": null, | ||
| "name": "status", | ||
| "doc": "Originator status: ACTIVE, PENDING, or INACTIVE", | ||
| "type": [ | ||
| "null", | ||
| "string" | ||
| ] | ||
| }, | ||
| { | ||
| "default": null, | ||
| "name": "originatorType", | ||
| "doc": "Code value for originator type (MERCHANT, BROKER, AFFILIATE, PLATFORM)", | ||
| "type": [ | ||
| "null", | ||
| "org.apache.fineract.avro.generic.v1.CodeValueDataV1" | ||
| ] | ||
| }, | ||
| { | ||
| "default": null, | ||
| "name": "channelType", | ||
| "doc": "Code value for channel type (ONLINE, IN_STORE, API, AGGREGATOR)", | ||
| "type": [ | ||
| "null", | ||
| "org.apache.fineract.avro.generic.v1.CodeValueDataV1" | ||
| ] | ||
| } | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
...in/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiConstants.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| /** | ||
| * 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 java.util.Set; | ||
|
|
||
| public final class LoanOriginatorApiConstants { | ||
|
|
||
| private LoanOriginatorApiConstants() {} | ||
|
|
||
| public static final String RESOURCE_NAME = "LOAN_ORIGINATOR"; | ||
| public static final String RESOURCE_PATH = "/loan-originators"; | ||
|
|
||
| public static final String ACTION_CREATE = "CREATE"; | ||
| public static final String ACTION_UPDATE = "UPDATE"; | ||
| public static final String ACTION_DELETE = "DELETE"; | ||
|
|
||
| public static final String ORIGINATOR_TYPE_CODE_NAME = "LoanOriginatorType"; | ||
| public static final String CHANNEL_TYPE_CODE_NAME = "LoanOriginationChannelType"; | ||
|
|
||
| public static final String EXTERNAL_ID_PARAM = "externalId"; | ||
| public static final String NAME_PARAM = "name"; | ||
| public static final String STATUS_PARAM = "status"; | ||
| public static final String ORIGINATOR_TYPE_ID_PARAM = "originatorTypeId"; | ||
| public static final String CHANNEL_TYPE_ID_PARAM = "channelTypeId"; | ||
|
|
||
| public static final Set<String> CREATE_REQUEST_PARAMS = Set.of(EXTERNAL_ID_PARAM, NAME_PARAM, STATUS_PARAM, ORIGINATOR_TYPE_ID_PARAM, | ||
| CHANNEL_TYPE_ID_PARAM); | ||
|
|
||
| public static final Set<String> UPDATE_REQUEST_PARAMS = Set.of(NAME_PARAM, STATUS_PARAM, ORIGINATOR_TYPE_ID_PARAM, | ||
| CHANNEL_TYPE_ID_PARAM); | ||
|
|
||
| public static final Set<String> RESPONSE_PARAMS = Set.of("id", EXTERNAL_ID_PARAM, NAME_PARAM, STATUS_PARAM, ORIGINATOR_TYPE_ID_PARAM, | ||
| CHANNEL_TYPE_ID_PARAM); | ||
| } |
173 changes: 173 additions & 0 deletions
173
...ain/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResource.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| /** | ||
| * 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.Operation; | ||
| import io.swagger.v3.oas.annotations.Parameter; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.ws.rs.Consumes; | ||
| import jakarta.ws.rs.DELETE; | ||
| 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.core.MediaType; | ||
| import java.util.List; | ||
| 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.data.CommandProcessingResult; | ||
| import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; | ||
| import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData; | ||
| import org.apache.fineract.portfolio.loanorigination.service.LoanOriginatorReadPlatformService; | ||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Path("/v1/loan-originators") | ||
| @Component | ||
| @ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true") | ||
| @Tag(name = "Loan Originators", description = "Manage loan originator details for revenue sharing and reporting") | ||
| @RequiredArgsConstructor | ||
| public class LoanOriginatorApiResource { | ||
|
|
||
| private final PlatformSecurityContext context; | ||
| private final LoanOriginatorReadPlatformService loanOriginatorReadPlatformService; | ||
| private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; | ||
|
|
||
| @POST | ||
| @Consumes({ 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.") | ||
| @ApiResponse(responseCode = "200", description = "OK") | ||
| @ApiResponse(responseCode = "400", description = "Required parameter is missing or incorrect format") | ||
| @ApiResponse(responseCode = "403", description = "Duplicate external ID or insufficient permissions") | ||
| public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) { | ||
| final CommandWrapper commandRequest = new CommandWrapperBuilder().createLoanOriginator().withJson(apiRequestBodyAsJson).build(); | ||
| return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); | ||
| } | ||
|
|
||
| @GET | ||
| @Consumes({ MediaType.APPLICATION_JSON }) | ||
| @Produces({ MediaType.APPLICATION_JSON }) | ||
| @Operation(summary = "List all loan originators", description = "Retrieves all loan originator records. Requires READ_LOAN_ORIGINATOR permission.") | ||
| @ApiResponse(responseCode = "200", description = "OK") | ||
| @ApiResponse(responseCode = "403", description = "Insufficient permissions") | ||
| public List<LoanOriginatorData> retrieveAll() { | ||
| this.context.authenticatedUser().validateHasReadPermission(LoanOriginatorApiConstants.RESOURCE_NAME); | ||
|
|
||
| return this.loanOriginatorReadPlatformService.retrieveAll(); | ||
| } | ||
|
|
||
| @GET | ||
| @Path("{originatorId}") | ||
| @Consumes({ 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.") | ||
| @ApiResponse(responseCode = "200", description = "OK") | ||
| @ApiResponse(responseCode = "403", description = "Insufficient permissions") | ||
| @ApiResponse(responseCode = "404", description = "Originator not found") | ||
| public LoanOriginatorData retrieveOne(@PathParam("originatorId") @Parameter(description = "originatorId") final Long originatorId) { | ||
| this.context.authenticatedUser().validateHasReadPermission(LoanOriginatorApiConstants.RESOURCE_NAME); | ||
|
|
||
| return this.loanOriginatorReadPlatformService.retrieveById(originatorId); | ||
| } | ||
|
|
||
| @GET | ||
| @Path("external-id/{externalId}") | ||
| @Consumes({ 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.") | ||
| @ApiResponse(responseCode = "200", description = "OK") | ||
| @ApiResponse(responseCode = "403", description = "Insufficient permissions") | ||
| @ApiResponse(responseCode = "404", description = "Originator not found") | ||
| public LoanOriginatorData retrieveByExternalId( | ||
| @PathParam("externalId") @Parameter(description = "externalId") final String externalId) { | ||
| this.context.authenticatedUser().validateHasReadPermission(LoanOriginatorApiConstants.RESOURCE_NAME); | ||
|
|
||
| return this.loanOriginatorReadPlatformService.retrieveByExternalId(externalId); | ||
| } | ||
|
|
||
| @PUT | ||
| @Path("{originatorId}") | ||
| @Consumes({ 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.") | ||
| @ApiResponse(responseCode = "200", description = "OK") | ||
| @ApiResponse(responseCode = "400", description = "Incorrect format") | ||
| @ApiResponse(responseCode = "403", description = "Insufficient permissions") | ||
| @ApiResponse(responseCode = "404", description = "Originator not found") | ||
| public CommandProcessingResult update(@PathParam("originatorId") @Parameter(description = "originatorId") final Long originatorId, | ||
| @Parameter(hidden = true) final String apiRequestBodyAsJson) { | ||
| final CommandWrapper commandRequest = new CommandWrapperBuilder().updateLoanOriginator(originatorId).withJson(apiRequestBodyAsJson) | ||
| .build(); | ||
| return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); | ||
| } | ||
|
|
||
| @PUT | ||
| @Path("external-id/{externalId}") | ||
| @Consumes({ 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.") | ||
| @ApiResponse(responseCode = "200", description = "OK") | ||
| @ApiResponse(responseCode = "400", description = "Incorrect format") | ||
| @ApiResponse(responseCode = "403", description = "Insufficient permissions") | ||
| @ApiResponse(responseCode = "404", description = "Originator not found") | ||
| public CommandProcessingResult updateByExternalId( | ||
| @PathParam("externalId") @Parameter(description = "externalId") final String externalId, | ||
| @Parameter(hidden = true) final String apiRequestBodyAsJson) { | ||
| final Long originatorId = this.loanOriginatorReadPlatformService.resolveIdByExternalId(externalId); | ||
|
|
||
| final CommandWrapper commandRequest = new CommandWrapperBuilder().updateLoanOriginator(originatorId).withJson(apiRequestBodyAsJson) | ||
| .build(); | ||
| return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); | ||
| } | ||
|
|
||
| @DELETE | ||
| @Path("{originatorId}") | ||
| @Consumes({ 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.") | ||
| @ApiResponse(responseCode = "200", description = "OK") | ||
| @ApiResponse(responseCode = "403", description = "Originator is mapped to loans and cannot be deleted, or insufficient permissions") | ||
| @ApiResponse(responseCode = "404", description = "Originator not found") | ||
| public CommandProcessingResult delete(@PathParam("originatorId") @Parameter(description = "originatorId") final Long originatorId) { | ||
| final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteLoanOriginator(originatorId).build(); | ||
| return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); | ||
| } | ||
|
|
||
| @DELETE | ||
| @Path("external-id/{externalId}") | ||
| @Consumes({ 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.") | ||
| @ApiResponse(responseCode = "200", description = "OK") | ||
| @ApiResponse(responseCode = "403", description = "Originator is mapped to loans and cannot be deleted, or insufficient permissions") | ||
| @ApiResponse(responseCode = "404", description = "Originator not found") | ||
| public CommandProcessingResult deleteByExternalId( | ||
| @PathParam("externalId") @Parameter(description = "externalId") final String externalId) { | ||
| final Long originatorId = this.loanOriginatorReadPlatformService.resolveIdByExternalId(externalId); | ||
|
|
||
| final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteLoanOriginator(originatorId).build(); | ||
| return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maintaining constants of CREATE, UPDATE, DELETE, LOAN_ORIGINATOR and /loan-originators/ may be more maintainable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on the current pattern of the file, I am not sure we should change it.