FINERACT-2418: add originator during the loan application

This commit is contained in:
Attila Budai
2026-02-09 14:40:34 +01:00
committed by Adam Saghy
parent f5951f1a7b
commit 99861ce93a
12 changed files with 767 additions and 5 deletions
@@ -48,6 +48,8 @@ import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
@@ -145,8 +147,7 @@ public class SavingsImportHandlerTest {
.setCellValue(savingsProductSheet.getRow(1).getCell(10).getStringCellValue());
firstSavingsRow.createCell(SavingsConstants.DECIMAL_PLACES_COL)
.setCellValue(savingsProductSheet.getRow(1).getCell(11).getNumericCellValue());
firstSavingsRow.createCell(SavingsConstants.IN_MULTIPLES_OF_COL)
.setCellValue(savingsProductSheet.getRow(1).getCell(12).getNumericCellValue());
safeNumericValueSetter(firstSavingsRow, SavingsConstants.IN_MULTIPLES_OF_COL, savingsProductSheet, 1, 12);
firstSavingsRow.createCell(SavingsConstants.NOMINAL_ANNUAL_INTEREST_RATE_COL)
.setCellValue(savingsProductSheet.getRow(1).getCell(2).getNumericCellValue());
firstSavingsRow.createCell(SavingsConstants.INTEREST_COMPOUNDING_PERIOD_COL)
@@ -181,7 +182,7 @@ public class SavingsImportHandlerTest {
Assertions.assertNotNull(importDocumentId);
// Wait for the creation of output excel
Thread.sleep(10000);
Thread.sleep(1000);
// check status column of output excel
String location = savingsAccountHelper.getOutputTemplateLocation(importDocumentId);
@@ -196,4 +197,18 @@ public class SavingsImportHandlerTest {
Assertions.assertEquals("Imported", row.getCell(SavingsConstants.STATUS_COL).getStringCellValue());
Outputworkbook.close();
}
private void safeNumericValueSetter(Row targetRow, int targetColId, Sheet sourceSheet, int rowId, int colId) {
Row row = sourceSheet.getRow(rowId);
if (row == null) {
targetRow.createCell(targetColId).setBlank();
} else {
Cell cell = row.getCell(colId);
if (cell == null || cell.getCellType() == CellType.BLANK) {
targetRow.createCell(targetColId).setBlank();
} else {
targetRow.createCell(targetColId).setCellValue(cell.getNumericCellValue());
}
}
}
}
@@ -0,0 +1,61 @@
/**
* 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.ok;
import java.util.List;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.models.GetGlobalConfigurationsResponse;
import org.apache.fineract.client.models.GlobalConfigurationPropertyData;
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
public class FeignGlobalConfigurationHelper {
private final FineractFeignClient fineractClient;
public FeignGlobalConfigurationHelper(FineractFeignClient fineractClient) {
this.fineractClient = fineractClient;
}
public void enableOriginatorCreationDuringLoanApplication() {
updateConfigurationByName("enable-originator-creation-during-loan-application", true);
}
public void disableOriginatorCreationDuringLoanApplication() {
updateConfigurationByName("enable-originator-creation-during-loan-application", false);
}
public void updateConfigurationByName(String configName, boolean enabled) {
Long configId = getConfigurationIdByName(configName);
ok(() -> fineractClient.globalConfiguration().updateConfiguration1(configId,
new PutGlobalConfigurationsRequest().enabled(enabled)));
}
public Long getConfigurationIdByName(String configName) {
List<GlobalConfigurationPropertyData> configs = getConfigurationList();
return configs.stream().filter(c -> configName.equals(c.getName())).findFirst().map(GlobalConfigurationPropertyData::getId)
.orElseThrow(() -> new RuntimeException("Configuration not found: " + configName));
}
private List<GlobalConfigurationPropertyData> getConfigurationList() {
GetGlobalConfigurationsResponse response = ok(() -> fineractClient.globalConfiguration().retrieveConfiguration(false));
return response.getGlobalConfiguration();
}
}
@@ -18,16 +18,20 @@
*/
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.math.BigDecimal;
import java.util.List;
import java.util.Map;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoansOriginatorData;
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostLoansResponse;
@@ -170,4 +174,43 @@ public class FeignLoanHelper {
.format(org.apache.fineract.integrationtests.common.Utils.getLocalDateOfTenant());
return createSubmittedLoan(clientId, productId, todayDate, 10000.0, 12);
}
public Long createSubmittedLoanWithOriginators(Long clientId, List<PostLoansOriginatorData> originators) {
PostLoansRequest request = buildSubmittedLoanRequest(clientId);
request.setOriginators(originators);
PostLoansResponse response = ok(() -> fineractClient.loans().calculateLoanScheduleOrSubmitLoanApplication(request, (String) null));
return response.getLoanId();
}
public CallFailedRuntimeException createSubmittedLoanWithOriginatorsExpectingError(Long clientId,
List<PostLoansOriginatorData> originators) {
PostLoansRequest request = buildSubmittedLoanRequest(clientId);
request.setOriginators(originators);
return fail(() -> fineractClient.loans().calculateLoanScheduleOrSubmitLoanApplication(request, (String) null));
}
private PostLoansRequest buildSubmittedLoanRequest(Long clientId) {
Long productId = createSimpleLoanProduct();
String todayDate = org.apache.fineract.integrationtests.common.Utils.dateFormatter
.format(org.apache.fineract.integrationtests.common.Utils.getLocalDateOfTenant());
return new PostLoansRequest()//
.clientId(clientId)//
.productId(productId)//
.loanType("individual")//
.submittedOnDate(todayDate)//
.expectedDisbursementDate(todayDate)//
.principal(BigDecimal.valueOf(10000.0))//
.loanTermFrequency(12)//
.loanTermFrequencyType(2)//
.numberOfRepayments(12)//
.repaymentEvery(1)//
.repaymentFrequencyType(2)//
.interestRatePerPeriod(BigDecimal.ZERO)//
.amortizationType(1)//
.interestType(0)//
.interestCalculationPeriodType(1)//
.transactionProcessingStrategyCode("mifos-standard-strategy")//
.locale("en")//
.dateFormat("dd MMMM yyyy");
}
}
@@ -0,0 +1,218 @@
/**
* 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.FineractFeignClient;
import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
import org.apache.fineract.client.models.PostLoansOriginatorData;
import org.apache.fineract.integrationtests.client.FeignIntegrationTest;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignClientHelper;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignGlobalConfigurationHelper;
import org.apache.fineract.integrationtests.client.feign.helpers.FeignLoanHelper;
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(2)
public class FeignLoanOriginatorDuringApplicationTest extends FeignIntegrationTest {
private static FeignLoanOriginatorHelper originatorHelper;
private static FeignClientHelper clientHelper;
private static FeignLoanHelper loanHelper;
private static FeignGlobalConfigurationHelper configHelper;
@BeforeAll
public static void setup() {
FineractFeignClient fineractClient = FineractFeignClientHelper.getFineractFeignClient();
originatorHelper = new FeignLoanOriginatorHelper(fineractClient);
clientHelper = new FeignClientHelper(fineractClient);
loanHelper = new FeignLoanHelper(fineractClient);
configHelper = new FeignGlobalConfigurationHelper(fineractClient);
}
@Test
public void testCreateLoanWithExistingOriginatorById() {
final String originatorExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(originatorExternalId);
final Long clientId = clientHelper.createClient();
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().id(originatorId));
final Long loanId = loanHelper.createSubmittedLoanWithOriginators(clientId, originators);
assertThat(loanId).isNotNull();
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
assertThat(loanDetails.getOriginators()).hasSize(1);
assertThat(loanDetails.getOriginators().get(0).getId()).isEqualTo(originatorId);
originatorHelper.detachOriginatorFromLoan(loanId, originatorId);
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testCreateLoanWithExistingOriginatorByExternalId() {
final String originatorExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(originatorExternalId);
final Long clientId = clientHelper.createClient();
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().externalId(originatorExternalId));
final Long loanId = loanHelper.createSubmittedLoanWithOriginators(clientId, originators);
assertThat(loanId).isNotNull();
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
assertThat(loanDetails.getOriginators()).hasSize(1);
assertThat(loanDetails.getOriginators().get(0).getExternalId()).isEqualTo(originatorExternalId);
originatorHelper.detachOriginatorFromLoan(loanId, originatorId);
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testCreateLoanWithNewOriginatorWhenConfigEnabled() {
configHelper.enableOriginatorCreationDuringLoanApplication();
try {
final Long clientId = clientHelper.createClient();
final String newOriginatorExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final List<PostLoansOriginatorData> originators = List
.of(new PostLoansOriginatorData().externalId(newOriginatorExternalId).name("New Merchant Created During Loan"));
final Long loanId = loanHelper.createSubmittedLoanWithOriginators(clientId, originators);
assertThat(loanId).isNotNull();
final var createdOriginator = originatorHelper.getOriginatorByExternalId(newOriginatorExternalId);
assertThat(createdOriginator).isNotNull();
assertThat(createdOriginator.getName()).isEqualTo("New Merchant Created During Loan");
assertThat(createdOriginator.getStatus()).isEqualTo("ACTIVE");
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
assertThat(loanDetails.getOriginators()).hasSize(1);
assertThat(loanDetails.getOriginators().get(0).getExternalId()).isEqualTo(newOriginatorExternalId);
originatorHelper.detachOriginatorFromLoan(loanId, createdOriginator.getId());
originatorHelper.deleteOriginator(createdOriginator.getId());
} finally {
configHelper.disableOriginatorCreationDuringLoanApplication();
}
}
@Test
public void testCreateLoanWithNewOriginatorFailsWhenConfigDisabled() {
configHelper.disableOriginatorCreationDuringLoanApplication();
final Long clientId = clientHelper.createClient();
final String nonExistingExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().externalId(nonExistingExternalId));
final CallFailedRuntimeException exception = loanHelper.createSubmittedLoanWithOriginatorsExpectingError(clientId, originators);
assertThat(exception.getStatus()).isIn(403, 404);
}
@Test
public void testCreateLoanWithMultipleOriginators() {
final String externalId1 = FeignLoanOriginatorHelper.generateUniqueExternalId();
final String externalId2 = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId1 = originatorHelper.createOriginator(externalId1);
final Long originatorId2 = originatorHelper.createOriginator(externalId2);
final Long clientId = clientHelper.createClient();
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().id(originatorId1),
new PostLoansOriginatorData().externalId(externalId2));
final Long loanId = loanHelper.createSubmittedLoanWithOriginators(clientId, originators);
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
assertThat(loanDetails.getOriginators()).hasSize(2);
originatorHelper.detachOriginatorFromLoan(loanId, originatorId1);
originatorHelper.detachOriginatorFromLoan(loanId, originatorId2);
originatorHelper.deleteOriginator(originatorId1);
originatorHelper.deleteOriginator(originatorId2);
}
@Test
public void testCreateLoanWithInvalidOriginatorDataReturns400() {
final Long clientId = clientHelper.createClient();
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().name("Invalid - no id or externalId"));
final CallFailedRuntimeException exception = loanHelper.createSubmittedLoanWithOriginatorsExpectingError(clientId, originators);
assertThat(exception.getStatus()).isEqualTo(400);
}
@Test
public void testCreateLoanWithInactiveOriginatorReturns403() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(externalId, "Inactive Originator", "INACTIVE");
final Long clientId = clientHelper.createClient();
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().id(originatorId));
final CallFailedRuntimeException exception = loanHelper.createSubmittedLoanWithOriginatorsExpectingError(clientId, originators);
assertThat(exception.getStatus()).isEqualTo(403);
originatorHelper.deleteOriginator(originatorId);
}
@Test
public void testCreateLoanWithNonExistingOriginatorIdReturns404() {
final Long clientId = clientHelper.createClient();
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().id(999999L));
final CallFailedRuntimeException exception = loanHelper.createSubmittedLoanWithOriginatorsExpectingError(clientId, originators);
assertThat(exception.getStatus()).isEqualTo(404);
}
@Test
public void testCreateLoanWithoutOriginatorsStillWorks() {
final Long clientId = clientHelper.createClient();
final Long loanId = loanHelper.createSubmittedLoan(clientId);
assertThat(loanId).isNotNull();
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
assertThat(loanDetails.getOriginators()).isEmpty();
}
@Test
public void testCreateLoanWithDuplicateOriginatorInListAttachesOnce() {
final String externalId = FeignLoanOriginatorHelper.generateUniqueExternalId();
final Long originatorId = originatorHelper.createOriginator(externalId);
final Long clientId = clientHelper.createClient();
final List<PostLoansOriginatorData> originators = List.of(new PostLoansOriginatorData().id(originatorId),
new PostLoansOriginatorData().externalId(externalId));
final Long loanId = loanHelper.createSubmittedLoanWithOriginators(clientId, originators);
final var loanDetails = loanHelper.getLoanDetailsWithAssociations(loanId, "originators");
assertThat(loanDetails.getOriginators()).hasSize(1);
originatorHelper.detachOriginatorFromLoan(loanId, originatorId);
originatorHelper.deleteOriginator(originatorId);
}
}