Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
import org.apache.fineract.client.feign.services.WorkingCapitalLoanDelinquencyActionsApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanDelinquencyRangeScheduleApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanInternalCobApiApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanOriginatorsApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanProductsApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanTransactionsApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoansApi;
Expand Down Expand Up @@ -807,6 +808,10 @@ public WorkingCapitalNearBreachApi workingCapitalNearBreaches() {
return create(WorkingCapitalNearBreachApi.class);
}

public WorkingCapitalLoanOriginatorsApi workingCapitalLoanOriginators() {
return create(WorkingCapitalLoanOriginatorsApi.class);
}

public WorkingDaysApi workingDays() {
return create(WorkingDaysApi.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ private CommandWrapperConstants() {}
public static final String ENTITY_DELINQUENCY_ACTION = "DELINQUENCY_ACTION";
public static final String ENTITY_LOAN_AVAILABLE_DISBURSEMENT_AMOUNT = "LOAN_AVAILABLE_DISBURSEMENT_AMOUNT";
public static final String ENTITY_LOAN_ORIGINATOR = "LOAN_ORIGINATOR";
public static final String ENTITY_WORKING_CAPITAL_LOAN_ORIGINATOR = "WORKING_CAPITAL_LOAN_ORIGINATOR";
public static final String ENTITY_WORKINGDAYS = "WORKINGDAYS";
public static final String ENTITY_SHAREPRODUCT = "SHAREPRODUCT";
public static final String ENTITY_INTEREST_PAUSE = "INTEREST_PAUSE";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@
import static org.apache.fineract.commands.domain.CommandWrapperConstants.ENTITY_WORKINGCAPITALLOAN;
import static org.apache.fineract.commands.domain.CommandWrapperConstants.ENTITY_WORKINGCAPITALLOANCHARGE;
import static org.apache.fineract.commands.domain.CommandWrapperConstants.ENTITY_WORKINGCAPITALLOANPRODUCT;
import static org.apache.fineract.commands.domain.CommandWrapperConstants.ENTITY_WORKING_CAPITAL_LOAN_ORIGINATOR;
import static org.apache.fineract.useradministration.service.AppUserConstants.PASSWORD;
import static org.apache.fineract.useradministration.service.AppUserConstants.REPEAT_PASSWORD;

Expand Down Expand Up @@ -4077,4 +4078,24 @@ public CommandWrapperBuilder undoAccountTransfer(final Long transferId) {
this.href = "/accounttransfers";
return this;
}

public CommandWrapperBuilder attachWorkingCapitalLoanOriginator(final Long loanId, final Long originatorId) {
this.actionName = ACTION_ATTACH;
this.entityName = ENTITY_WORKING_CAPITAL_LOAN_ORIGINATOR;
this.entityId = loanId;
this.loanId = loanId;
this.subentityId = originatorId;
this.href = "/working-capital-loans/" + loanId + "/originators/" + originatorId;
return this;
}

public CommandWrapperBuilder detachWorkingCapitalLoanOriginator(final Long loanId, final Long originatorId) {
this.actionName = ACTION_DETACH;
this.entityName = ENTITY_WORKING_CAPITAL_LOAN_ORIGINATOR;
this.entityId = loanId;
this.loanId = loanId;
this.subentityId = originatorId;
this.href = "/working-capital-loans/" + loanId + "/originators/" + originatorId;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.Consumes;
Expand Down Expand Up @@ -64,7 +66,7 @@ public class LoanOriginatorsApiResource {
@Path("{loanId}/originators")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve originators for a loan by loan ID", description = "Retrieves all originators attached to a specific loan. Requires READ_LOAN permission.")
@ApiResponse(responseCode = "200", description = "OK - Returns wrapped list of originators (may be empty)")
@ApiResponse(responseCode = "200", description = "OK - Returns wrapped list of originators (may be empty)", content = @Content(schema = @Schema(implementation = LoanOriginatorsResponse.class)))
@ApiResponse(responseCode = "403", description = "Insufficient permissions")
@ApiResponse(responseCode = "404", description = "Loan not found")
public LoanOriginatorsResponse retrieveOriginatorsByLoanId(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId) {
Expand All @@ -81,7 +83,7 @@ public LoanOriginatorsResponse retrieveOriginatorsByLoanId(@PathParam("loanId")
@Path("external-id/{loanExternalId}/originators")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve originators for a loan by loan external ID", description = "Retrieves all originators attached to a specific loan using loan external ID. Requires READ_LOAN permission.")
@ApiResponse(responseCode = "200", description = "OK - Returns wrapped list of originators (may be empty)")
@ApiResponse(responseCode = "200", description = "OK - Returns wrapped list of originators (may be empty)", content = @Content(schema = @Schema(implementation = LoanOriginatorsResponse.class)))
@ApiResponse(responseCode = "403", description = "Insufficient permissions")
@ApiResponse(responseCode = "404", description = "Loan not found")
public LoanOriginatorsResponse retrieveOriginatorsByLoanExternalId(
Expand All @@ -102,7 +104,7 @@ public LoanOriginatorsResponse retrieveOriginatorsByLoanExternalId(
@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 = "200", description = "OK - Originator attached", content = @Content(schema = @Schema(implementation = LoanOriginatorMappingResponse.class)))
@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,
Expand All @@ -119,7 +121,7 @@ public LoanOriginatorMappingResponse attachOriginatorToLoan(@PathParam("loanId")
@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 = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorMappingResponse.class)))
@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(
Expand All @@ -139,7 +141,7 @@ public LoanOriginatorMappingResponse attachOriginatorToLoanByOriginatorExternalI
@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 = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorMappingResponse.class)))
@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(
Expand All @@ -163,7 +165,7 @@ public LoanOriginatorMappingResponse attachOriginatorToLoanByLoanExternalId(
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Attach originator to loan by external IDs")
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorMappingResponse.class)))
@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(
Expand All @@ -188,7 +190,7 @@ public LoanOriginatorMappingResponse attachOriginatorToLoanByExternalIds(
@Path("{loanId}/originators/{originatorId}")
@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 = "200", description = "OK - Originator detached", content = @Content(schema = @Schema(implementation = LoanOriginatorMappingResponse.class)))
@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,
Expand All @@ -204,7 +206,7 @@ public LoanOriginatorMappingResponse detachOriginatorFromLoan(@PathParam("loanId
@Path("{loanId}/originators/external-id/{originatorExternalId}")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Detach originator from loan by loan ID and originator external ID")
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorMappingResponse.class)))
@ApiResponse(responseCode = "403", description = "Loan not in correct status")
@ApiResponse(responseCode = "404", description = "Loan, originator, or mapping not found")
public LoanOriginatorMappingResponse detachOriginatorFromLoanByOriginatorExternalId(
Expand All @@ -223,7 +225,7 @@ public LoanOriginatorMappingResponse detachOriginatorFromLoanByOriginatorExterna
@Path("external-id/{loanExternalId}/originators/{originatorId}")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Detach originator from loan by loan external ID and originator ID")
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorMappingResponse.class)))
@ApiResponse(responseCode = "403", description = "Loan not in correct status")
@ApiResponse(responseCode = "404", description = "Loan, originator, or mapping not found")
public LoanOriginatorMappingResponse detachOriginatorFromLoanByLoanExternalId(
Expand All @@ -246,7 +248,7 @@ public LoanOriginatorMappingResponse detachOriginatorFromLoanByLoanExternalId(
@Path("external-id/{loanExternalId}/originators/external-id/{originatorExternalId}")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Detach originator from loan by external IDs")
@ApiResponse(responseCode = "200", description = "OK")
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorMappingResponse.class)))
@ApiResponse(responseCode = "403", description = "Loan not in correct status")
@ApiResponse(responseCode = "404", description = "Loan, originator, or mapping not found")
public LoanOriginatorMappingResponse detachOriginatorFromLoanByExternalIds(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanorigination.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;

@Getter
@Setter
@Entity
@NoArgsConstructor
@Table(name = "m_wc_loan_originator_mapping")
public class WorkingCapitalLoanOriginatorMapping extends AbstractAuditableWithUTCDateTimeCustom<Long> {

@Column(name = "loan_id", nullable = false)
private Long loanId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "originator_id", nullable = false)
private LoanOriginator originator;

public static WorkingCapitalLoanOriginatorMapping create(Long loanId, LoanOriginator originator) {
WorkingCapitalLoanOriginatorMapping mapping = new WorkingCapitalLoanOriginatorMapping();
mapping.setLoanId(loanId);
mapping.setOriginator(originator);
return mapping;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanorigination.domain;

import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

@Repository
public interface WorkingCapitalLoanOriginatorMappingRepository
extends JpaRepository<WorkingCapitalLoanOriginatorMapping, Long>, JpaSpecificationExecutor<WorkingCapitalLoanOriginatorMapping> {

Optional<WorkingCapitalLoanOriginatorMapping> findByLoanIdAndOriginatorId(Long loanId, Long originatorId);

boolean existsByLoanIdAndOriginatorId(Long loanId, Long originatorId);

@org.springframework.data.jpa.repository.Query("""
SELECT m FROM WorkingCapitalLoanOriginatorMapping m
JOIN FETCH m.originator o
LEFT JOIN FETCH o.originatorType
LEFT JOIN FETCH o.channelType
WHERE m.loanId = :loanId
""")
List<WorkingCapitalLoanOriginatorMapping> findByLoanIdWithOriginator(
@org.springframework.data.repository.query.Param("loanId") Long loanId);
}
Original file line number Diff line number Diff line change
@@ -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 WorkingCapitalLoanOriginatorMappingNotFoundException extends AbstractPlatformResourceNotFoundException {

public WorkingCapitalLoanOriginatorMappingNotFoundException(Long loanId, Long originatorId) {
super("error.msg.wc.loan.originator.mapping.not.found",
"Originator with id " + originatorId + " is not attached to working capital loan with id " + loanId, loanId, originatorId);
}
}
Loading