FINERACT-2418: add originatior loan mapping

This commit is contained in:
Attila Budai
2026-02-03 14:58:28 +01:00
committed by Adam Saghy
parent 3dbada34f7
commit 3b5ea96f5e
18 changed files with 749 additions and 447 deletions
@@ -23,17 +23,24 @@ 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.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
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.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorMappingResponse;
import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorsResponse;
import org.apache.fineract.portfolio.loanorigination.service.LoanOriginatorReadPlatformService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -51,6 +58,7 @@ public class LoanOriginatorsApiResource {
private final PlatformSecurityContext context;
private final LoanReadPlatformService loanReadPlatformService;
private final LoanOriginatorReadPlatformService loanOriginatorReadPlatformService;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@GET
@Path("{loanId}/originators")
@@ -90,4 +98,184 @@ public class LoanOriginatorsApiResource {
return LoanOriginatorsResponse.of(this.loanOriginatorReadPlatformService.retrieveByLoanId(loanId));
}
@POST
@Path("{loanId}/originators/{originatorId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Attach originator to loan by IDs", description = "Attaches an originator to a loan. Loan must be in 'Submitted and Pending Approval' status. Requires ATTACH_LOAN_ORIGINATOR permission.")
@ApiResponse(responseCode = "200", description = "OK - Originator attached")
@ApiResponse(responseCode = "403", description = "Loan not in correct status, originator not ACTIVE, duplicate mapping, or insufficient permissions")
@ApiResponse(responseCode = "404", description = "Loan or originator not found")
public LoanOriginatorMappingResponse attachOriginatorToLoan(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("originatorId") @Parameter(description = "originatorId") final Long originatorId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().attachLoanOriginator(loanId, originatorId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return buildMappingResponse(result);
}
@POST
@Path("{loanId}/originators/external-id/{originatorExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Attach originator to loan by loan ID and originator external ID")
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "403", description = "Loan not in correct status, originator not ACTIVE, duplicate mapping")
@ApiResponse(responseCode = "404", description = "Loan or originator not found")
public LoanOriginatorMappingResponse attachOriginatorToLoanByOriginatorExternalId(
@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("originatorExternalId") @Parameter(description = "originatorExternalId") final String originatorExternalId) {
final Long originatorId = this.loanOriginatorReadPlatformService.resolveIdByExternalId(originatorExternalId);
final CommandWrapper commandRequest = new CommandWrapperBuilder().attachLoanOriginator(loanId, originatorId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return buildMappingResponse(result);
}
@POST
@Path("external-id/{loanExternalId}/originators/{originatorId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Attach originator to loan by loan external ID and originator ID")
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "403", description = "Loan not in correct status, originator not ACTIVE, duplicate mapping")
@ApiResponse(responseCode = "404", description = "Loan or originator not found")
public LoanOriginatorMappingResponse attachOriginatorToLoanByLoanExternalId(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("originatorId") @Parameter(description = "originatorId") final Long originatorId) {
final ExternalId externalId = ExternalIdFactory.produce(loanExternalId);
final Long loanId = this.loanReadPlatformService.retrieveLoanIdByExternalId(externalId);
if (loanId == null) {
throw new LoanNotFoundException(externalId);
}
final CommandWrapper commandRequest = new CommandWrapperBuilder().attachLoanOriginator(loanId, originatorId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return buildMappingResponse(result);
}
@POST
@Path("external-id/{loanExternalId}/originators/external-id/{originatorExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Attach originator to loan by external IDs")
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "403", description = "Loan not in correct status, originator not ACTIVE, duplicate mapping")
@ApiResponse(responseCode = "404", description = "Loan or originator not found")
public LoanOriginatorMappingResponse attachOriginatorToLoanByExternalIds(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("originatorExternalId") @Parameter(description = "originatorExternalId") final String originatorExternalId) {
final ExternalId loanExtId = ExternalIdFactory.produce(loanExternalId);
final Long loanId = this.loanReadPlatformService.retrieveLoanIdByExternalId(loanExtId);
if (loanId == null) {
throw new LoanNotFoundException(loanExtId);
}
final Long originatorId = this.loanOriginatorReadPlatformService.resolveIdByExternalId(originatorExternalId);
final CommandWrapper commandRequest = new CommandWrapperBuilder().attachLoanOriginator(loanId, originatorId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return buildMappingResponse(result);
}
@DELETE
@Path("{loanId}/originators/{originatorId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Detach originator from loan by IDs", description = "Detaches an originator from a loan. Loan must be in 'Submitted and Pending Approval' status. Requires DETACH_LOAN_ORIGINATOR permission.")
@ApiResponse(responseCode = "200", description = "OK - Originator detached")
@ApiResponse(responseCode = "403", description = "Loan not in correct status or insufficient permissions")
@ApiResponse(responseCode = "404", description = "Loan, originator, or mapping not found")
public LoanOriginatorMappingResponse detachOriginatorFromLoan(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("originatorId") @Parameter(description = "originatorId") final Long originatorId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().detachLoanOriginator(loanId, originatorId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return buildMappingResponse(result);
}
@DELETE
@Path("{loanId}/originators/external-id/{originatorExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Detach originator from loan by loan ID and originator external ID")
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "403", description = "Loan not in correct status")
@ApiResponse(responseCode = "404", description = "Loan, originator, or mapping not found")
public LoanOriginatorMappingResponse detachOriginatorFromLoanByOriginatorExternalId(
@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("originatorExternalId") @Parameter(description = "originatorExternalId") final String originatorExternalId) {
final Long originatorId = this.loanOriginatorReadPlatformService.resolveIdByExternalId(originatorExternalId);
final CommandWrapper commandRequest = new CommandWrapperBuilder().detachLoanOriginator(loanId, originatorId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return buildMappingResponse(result);
}
@DELETE
@Path("external-id/{loanExternalId}/originators/{originatorId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Detach originator from loan by loan external ID and originator ID")
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "403", description = "Loan not in correct status")
@ApiResponse(responseCode = "404", description = "Loan, originator, or mapping not found")
public LoanOriginatorMappingResponse detachOriginatorFromLoanByLoanExternalId(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("originatorId") @Parameter(description = "originatorId") final Long originatorId) {
final ExternalId externalId = ExternalIdFactory.produce(loanExternalId);
final Long loanId = this.loanReadPlatformService.retrieveLoanIdByExternalId(externalId);
if (loanId == null) {
throw new LoanNotFoundException(externalId);
}
final CommandWrapper commandRequest = new CommandWrapperBuilder().detachLoanOriginator(loanId, originatorId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return buildMappingResponse(result);
}
@DELETE
@Path("external-id/{loanExternalId}/originators/external-id/{originatorExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Detach originator from loan by external IDs")
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "403", description = "Loan not in correct status")
@ApiResponse(responseCode = "404", description = "Loan, originator, or mapping not found")
public LoanOriginatorMappingResponse detachOriginatorFromLoanByExternalIds(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("originatorExternalId") @Parameter(description = "originatorExternalId") final String originatorExternalId) {
final ExternalId loanExtId = ExternalIdFactory.produce(loanExternalId);
final Long loanId = this.loanReadPlatformService.retrieveLoanIdByExternalId(loanExtId);
if (loanId == null) {
throw new LoanNotFoundException(loanExtId);
}
final Long originatorId = this.loanOriginatorReadPlatformService.resolveIdByExternalId(originatorExternalId);
final CommandWrapper commandRequest = new CommandWrapperBuilder().detachLoanOriginator(loanId, originatorId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return buildMappingResponse(result);
}
private LoanOriginatorMappingResponse buildMappingResponse(final CommandProcessingResult result) {
return LoanOriginatorMappingResponse.of(result.getResourceId(),
result.getResourceExternalId() != null ? result.getResourceExternalId().getValue() : null, result.getSubResourceId(),
result.getSubResourceExternalId() != null ? result.getSubResourceExternalId().getValue() : null);
}
}
@@ -0,0 +1,46 @@
/**
* 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.data;
import java.io.Serial;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoanOriginatorMappingResponse implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long loanId;
private String loanExternalId;
private Long originatorId;
private String originatorExternalId;
public static LoanOriginatorMappingResponse of(Long loanId, String loanExternalId, Long originatorId, String originatorExternalId) {
return LoanOriginatorMappingResponse.builder().loanId(loanId).loanExternalId(loanExternalId).originatorId(originatorId)
.originatorExternalId(originatorExternalId).build();
}
}
@@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanorigination.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanNotInSubmittedStatusException extends AbstractPlatformDomainRuleException {
public LoanNotInSubmittedStatusException(Long loanId, String currentStatus) {
super("error.msg.loan.not.in.submitted.status",
"Loan with id " + loanId + " has status " + currentStatus
+ ". Originator can only be attached/detached while loan is in 'Submitted and Pending Approval' status.",
loanId, currentStatus);
}
}
@@ -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 LoanOriginatorMappingAlreadyExistsException extends AbstractPlatformDomainRuleException {
public LoanOriginatorMappingAlreadyExistsException(Long loanId, Long originatorId) {
super("error.msg.loan.originator.mapping.already.exists",
"Originator with id " + originatorId + " is already attached to loan with id " + loanId, loanId, originatorId);
}
}
@@ -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.AbstractPlatformResourceNotFoundException;
public class LoanOriginatorMappingNotFoundException extends AbstractPlatformResourceNotFoundException {
public LoanOriginatorMappingNotFoundException(Long loanId, Long originatorId) {
super("error.msg.loan.originator.mapping.not.found",
"Originator with id " + originatorId + " is not attached to loan with id " + loanId, loanId, originatorId);
}
}
@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanorigination.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanOriginatorNotActiveException extends AbstractPlatformDomainRuleException {
public LoanOriginatorNotActiveException(Long originatorId, String status) {
super("error.msg.loan.originator.not.active",
"Originator with id " + originatorId + " has status " + status + ". Only ACTIVE originators can be attached to loans.",
originatorId, status);
}
}
@@ -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 = "ATTACH")
@RequiredArgsConstructor
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public class AttachLoanOriginatorCommandHandler implements NewCommandSourceHandler {
private final LoanOriginatorWritePlatformService writePlatformService;
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.attachOriginatorToLoan(command.getLoanId(), command.subentityId());
}
}
@@ -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 = "DETACH")
@RequiredArgsConstructor
@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true")
public class DetachLoanOriginatorCommandHandler implements NewCommandSourceHandler {
private final LoanOriginatorWritePlatformService writePlatformService;
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.detachOriginatorFromLoan(command.getLoanId(), command.subentityId());
}
}
@@ -28,4 +28,8 @@ public interface LoanOriginatorWritePlatformService {
CommandProcessingResult update(Long id, JsonCommand command);
CommandProcessingResult delete(Long id);
CommandProcessingResult attachOriginatorToLoan(Long loanId, Long originatorId);
CommandProcessingResult detachOriginatorFromLoan(Long loanId, Long originatorId);
}
@@ -35,12 +35,19 @@ 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.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator;
import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMapping;
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.LoanNotInSubmittedStatusException;
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorCannotBeDeletedException;
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorDuplicateExternalIdException;
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorMappingAlreadyExistsException;
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorMappingNotFoundException;
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorNotActiveException;
import org.apache.fineract.portfolio.loanorigination.exception.LoanOriginatorNotFoundException;
import org.apache.fineract.portfolio.loanorigination.serialization.LoanOriginatorDataValidator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -57,6 +64,7 @@ public class LoanOriginatorWritePlatformServiceImpl implements LoanOriginatorWri
private final LoanOriginatorMappingRepository loanOriginatorMappingRepository;
private final LoanOriginatorDataValidator loanOriginatorDataValidator;
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
private final LoanRepositoryWrapper loanRepositoryWrapper;
@Override
public CommandProcessingResult create(final JsonCommand command) {
@@ -144,6 +152,52 @@ public class LoanOriginatorWritePlatformServiceImpl implements LoanOriginatorWri
return new CommandProcessingResultBuilder().withEntityId(id).withEntityExternalId(externalId).build();
}
@Override
public CommandProcessingResult attachOriginatorToLoan(final Long loanId, final Long originatorId) {
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
if (!loan.isSubmittedAndPendingApproval()) {
throw new LoanNotInSubmittedStatusException(loanId, loan.getStatus().getCode());
}
final LoanOriginator originator = this.loanOriginatorRepository.findById(originatorId)
.orElseThrow(() -> new LoanOriginatorNotFoundException(originatorId));
if (originator.getStatus() != LoanOriginatorStatus.ACTIVE) {
throw new LoanOriginatorNotActiveException(originatorId, originator.getStatus().getValue());
}
if (this.loanOriginatorMappingRepository.existsByLoanIdAndOriginatorId(loanId, originatorId)) {
throw new LoanOriginatorMappingAlreadyExistsException(loanId, originatorId);
}
final LoanOriginatorMapping mapping = LoanOriginatorMapping.create(loanId, originator);
this.loanOriginatorMappingRepository.saveAndFlush(mapping);
return new CommandProcessingResultBuilder().withEntityId(loanId).withEntityExternalId(loan.getExternalId())
.withSubEntityId(originatorId).withSubEntityExternalId(originator.getExternalId()).build();
}
@Override
public CommandProcessingResult detachOriginatorFromLoan(final Long loanId, final Long originatorId) {
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
if (!loan.isSubmittedAndPendingApproval()) {
throw new LoanNotInSubmittedStatusException(loanId, loan.getStatus().getCode());
}
final LoanOriginator originator = this.loanOriginatorRepository.findById(originatorId)
.orElseThrow(() -> new LoanOriginatorNotFoundException(originatorId));
final LoanOriginatorMapping mapping = this.loanOriginatorMappingRepository.findByLoanIdAndOriginatorId(loanId, originatorId)
.orElseThrow(() -> new LoanOriginatorMappingNotFoundException(loanId, originatorId));
this.loanOriginatorMappingRepository.delete(mapping);
return new CommandProcessingResultBuilder().withEntityId(loanId).withEntityExternalId(loan.getExternalId())
.withSubEntityId(originatorId).withSubEntityExternalId(originator.getExternalId()).build();
}
private CodeValue resolveCodeValue(final JsonCommand command, final String paramName, final String codeName) {
final Long codeValueId = command.longValueOfParameterNamed(paramName);
if (codeValueId == null) {
@@ -24,4 +24,5 @@
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/0002_permissions.xml"/>
<include relativeToChangelogFile="true" file="parts/0003_mapping_permissions.xml"/>
</databaseChangeLog>
@@ -0,0 +1,50 @@
<?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-mapping-perm-001" author="fineract">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(1) FROM m_permission WHERE code = 'ATTACH_LOAN_ORIGINATOR'</sqlCheck>
</preConditions>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="ATTACH_LOAN_ORIGINATOR"/>
<column name="entity_name" value="LOAN_ORIGINATOR"/>
<column name="action_name" value="ATTACH"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
<changeSet id="loan-origination-mapping-perm-002" author="fineract">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(1) FROM m_permission WHERE code = 'DETACH_LOAN_ORIGINATOR'</sqlCheck>
</preConditions>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="DETACH_LOAN_ORIGINATOR"/>
<column name="entity_name" value="LOAN_ORIGINATOR"/>
<column name="action_name" value="DETACH"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
</databaseChangeLog>