FINERACT-2418: add originatior association

This commit is contained in:
Attila Budai
2026-02-03 14:58:28 +01:00
committed by Adam Saghy
parent 3b5ea96f5e
commit ef12056e5d
7 changed files with 99 additions and 4 deletions
@@ -54,6 +54,7 @@ public final class DataTableApiConstant {
public static final String meetingAssociateParamName = "meeting"; public static final String meetingAssociateParamName = "meeting";
public static final String emiAmountVariationsAssociateParamName = "emiAmountVariations"; public static final String emiAmountVariationsAssociateParamName = "emiAmountVariations";
public static final String collectionAssociateParamName = "collection"; public static final String collectionAssociateParamName = "collection";
public static final String originatorsAssociateParamName = "originators";
public static final String TABLE_COLUMN_CODE_MAPPINGS = "x_table_column_code_mappings"; public static final String TABLE_COLUMN_CODE_MAPPINGS = "x_table_column_code_mappings";
public static final String TABLE_REGISTERED_TABLE = "x_registered_table"; public static final String TABLE_REGISTERED_TABLE = "x_registered_table";
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
@@ -16,6 +17,7 @@
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
--> -->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -50,6 +50,7 @@ import org.apache.fineract.portfolio.group.data.GroupGeneralData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.guarantor.data.IGuarantor; import org.apache.fineract.portfolio.loanaccount.guarantor.data.IGuarantor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData; import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
@@ -151,6 +152,7 @@ public class LoanAccountData {
private Collection<NoteData> notes; private Collection<NoteData> notes;
private Collection<DisbursementData> disbursementDetails; private Collection<DisbursementData> disbursementDetails;
private LoanScheduleData originalSchedule; private LoanScheduleData originalSchedule;
private Collection<LoanOriginatorData> originators;
// template // template
private Collection<LoanProductData> productOptions; private Collection<LoanProductData> productOptions;
private Collection<StaffData> loanOfficerOptions; private Collection<StaffData> loanOfficerOptions;
@@ -57,6 +57,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.domain.CommandWrapper;
@@ -165,6 +166,8 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformS
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryDataProvider; import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryDataProvider;
import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryProviderDelegate; import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryProviderDelegate;
import org.apache.fineract.portfolio.loanorigination.data.LoanOriginatorData;
import org.apache.fineract.portfolio.loanorigination.service.LoanOriginatorReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData; import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
@@ -255,9 +258,9 @@ public class LoansApiResource {
"repaymentFrequencyNthDayTypeOptions", "repaymentFrequencyDaysOfWeekTypeOptions", "termFrequencyTypeOptions", "repaymentFrequencyNthDayTypeOptions", "repaymentFrequencyDaysOfWeekTypeOptions", "termFrequencyTypeOptions",
"interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions", "loanOfficerOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions", "loanOfficerOptions",
"loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions", "syncDisbursementWithMeeting", "loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions", "syncDisbursementWithMeeting",
"loanCounter", "loanProductCounter", "notes", "accountLinkingOptions", "linkedAccount", "interestRateDifferential", "loanCounter", "loanProductCounter", "notes", "originators", "accountLinkingOptions", "linkedAccount",
"isFloatingInterestRate", "interestRatesPeriods", "lastClosedBusinessDate", LoanApiConstants.canUseForTopup, "interestRateDifferential", "isFloatingInterestRate", "interestRatesPeriods", "lastClosedBusinessDate",
LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.topupAmount, LoanApiConstants.canUseForTopup, LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.topupAmount,
LoanApiConstants.clientActiveLoanOptions, LoanApiConstants.datatables, LoanProductConstants.RATES_PARAM_NAME, LoanApiConstants.clientActiveLoanOptions, LoanApiConstants.datatables, LoanProductConstants.RATES_PARAM_NAME,
LoanApiConstants.MULTIDISBURSE_DETAILS_PARAMNAME, LoanApiConstants.EMI_AMOUNT_VARIATIONS_PARAMNAME, LoanApiConstants.MULTIDISBURSE_DETAILS_PARAMNAME, LoanApiConstants.EMI_AMOUNT_VARIATIONS_PARAMNAME,
LoanApiConstants.COLLECTION_PARAMNAME, LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE, LoanApiConstants.COLLECTION_PARAMNAME, LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE,
@@ -310,6 +313,7 @@ public class LoansApiResource {
private final LoanSummaryProviderDelegate loanSummaryProviderDelegate; private final LoanSummaryProviderDelegate loanSummaryProviderDelegate;
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository; private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
private final LoanApprovedAmountHistoryRepository loanApprovedAmountHistoryRepository; private final LoanApprovedAmountHistoryRepository loanApprovedAmountHistoryRepository;
private final Optional<LoanOriginatorReadPlatformService> loanOriginatorReadPlatformService;
/* /*
* This template API is used for loan approval, ideally this should be invoked on loan that are pending for * This template API is used for loan approval, ideally this should be invoked on loan that are pending for
@@ -1052,7 +1056,7 @@ public class LoansApiResource {
DataTableApiConstant.guarantorsAssociateParamName, DataTableApiConstant.collateralAssociateParamName, DataTableApiConstant.guarantorsAssociateParamName, DataTableApiConstant.collateralAssociateParamName,
DataTableApiConstant.notesAssociateParamName, DataTableApiConstant.linkedAccountAssociateParamName, DataTableApiConstant.notesAssociateParamName, DataTableApiConstant.linkedAccountAssociateParamName,
DataTableApiConstant.multiDisburseDetailsAssociateParamName, DataTableApiConstant.collectionAssociateParamName, DataTableApiConstant.multiDisburseDetailsAssociateParamName, DataTableApiConstant.collectionAssociateParamName,
DataTableApiConstant.loanTermVariationsAssociateParamName)); DataTableApiConstant.loanTermVariationsAssociateParamName, DataTableApiConstant.originatorsAssociateParamName));
} }
ApiParameterHelper.excludeAssociationsForResponseIfProvided(exclude, associationParameters); ApiParameterHelper.excludeAssociationsForResponseIfProvided(exclude, associationParameters);
@@ -1158,6 +1162,14 @@ public class LoansApiResource {
mandatoryResponseParameters.add(DataTableApiConstant.linkedAccountAssociateParamName); mandatoryResponseParameters.add(DataTableApiConstant.linkedAccountAssociateParamName);
linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(resolvedLoanId); linkedAccount = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(resolvedLoanId);
} }
if (associationParameters.contains(DataTableApiConstant.originatorsAssociateParamName)) {
mandatoryResponseParameters.add(DataTableApiConstant.originatorsAssociateParamName);
if (loanOriginatorReadPlatformService.isPresent()) {
List<LoanOriginatorData> originatorList = loanOriginatorReadPlatformService.get().retrieveByLoanId(resolvedLoanId);
loanBasicDetails.setOriginators(originatorList.isEmpty() ? Collections.emptyList() : originatorList);
}
}
} }
Collection<LoanProductData> productOptions = null; Collection<LoanProductData> productOptions = null;
@@ -90,6 +90,14 @@ public class FeignLoanHelper {
return ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", "all", "exclude", "guarantors,futureSchedule"))); return ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", "all", "exclude", "guarantors,futureSchedule")));
} }
public GetLoansLoanIdResponse getLoanDetailsWithAssociations(Long loanId, String associations) {
return ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", associations)));
}
public GetLoansLoanIdResponse getLoanDetailsWithAssociationsAndExclude(Long loanId, String associations, String exclude) {
return ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", associations, "exclude", exclude)));
}
public void undoApproval(Long loanId) { public void undoApproval(Long loanId) {
PostLoansLoanIdRequest request = new PostLoansLoanIdRequest(); PostLoansLoanIdRequest request = new PostLoansLoanIdRequest();
ok(() -> fineractClient.loans().stateTransitions(loanId, request, Map.of("command", "undoApproval"))); ok(() -> fineractClient.loans().stateTransitions(loanId, request, Map.of("command", "undoApproval")));
@@ -343,4 +343,74 @@ public class FeignLoanOriginatorApiTest extends FeignIntegrationTest {
originatorHelper.deleteOriginator(originatorId); originatorHelper.deleteOriginator(originatorId);
} }
@Test
public void testRetrieveLoanWithOriginatorsAssociation() {
final String originatorExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(originatorExternalId);
final Long clientId = clientHelper.createClient("Test", "Client");
final Long loanId = loanHelper.createSubmittedLoan(clientId);
originatorHelper.attachOriginatorToLoan(loanId, originatorId);
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
assertThat(loanDetails.getOriginators()).isNotNull();
assertThat(loanDetails.getOriginators()).hasSize(1);
assertThat(loanDetails.getOriginators().get(0).getId()).isEqualTo(originatorId);
assertThat(loanDetails.getOriginators().get(0).getExternalId()).isEqualTo(originatorExternalId);
originatorHelper.detachOriginatorFromLoan(loanId, originatorId);
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testRetrieveLoanWithAllAssociationsIncludesOriginators() {
final String originatorExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(originatorExternalId);
final Long clientId = clientHelper.createClient("Test", "Client");
final Long loanId = loanHelper.createSubmittedLoan(clientId);
originatorHelper.attachOriginatorToLoan(loanId, originatorId);
final var loanDetails = loanHelper.getLoanDetailsWithAssociationsAndExclude(loanId, "all", "guarantors,futureSchedule");
assertThat(loanDetails.getOriginators()).isNotNull();
assertThat(loanDetails.getOriginators()).isNotEmpty();
assertThat(loanDetails.getOriginators().get(0).getId()).isEqualTo(originatorId);
originatorHelper.detachOriginatorFromLoan(loanId, originatorId);
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testRetrieveLoanWithNoOriginatorsReturnsEmptyList() {
final Long clientId = clientHelper.createClient("Test", "Client");
final Long loanId = loanHelper.createSubmittedLoan(clientId);
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
assertThat(loanDetails.getOriginators()).isNotNull();
assertThat(loanDetails.getOriginators()).isEmpty();
}
@Test
public void testRetrieveLoanExcludeOriginatorsFromAll() {
final String originatorExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(originatorExternalId);
final Long clientId = clientHelper.createClient("Test", "Client");
final Long loanId = loanHelper.createSubmittedLoan(clientId);
originatorHelper.attachOriginatorToLoan(loanId, originatorId);
final var loanDetails = loanHelper.getLoanDetailsWithAssociationsAndExclude(loanId, "all", "originators,guarantors,futureSchedule");
assertThat(loanDetails.getOriginators()).isNull();
originatorHelper.detachOriginatorFromLoan(loanId, originatorId);
originatorHelper.deleteOriginator(originatorId);
}
} }