Skip to content
Draft
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 @@ -155,6 +155,7 @@
import org.apache.fineract.client.feign.services.UserGeneratedDocumentsApi;
import org.apache.fineract.client.feign.services.UsersApi;
import org.apache.fineract.client.feign.services.WorkingCapitalBreachApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanBreachScheduleApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanCobCatchUpApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanDelinquencyActionsApi;
import org.apache.fineract.client.feign.services.WorkingCapitalLoanDelinquencyRangeScheduleApi;
Expand Down Expand Up @@ -766,6 +767,10 @@ public WorkingCapitalLoanDelinquencyRangeScheduleApi workingCapitalLoanDelinquen
return create(WorkingCapitalLoanDelinquencyRangeScheduleApi.class);
}

public WorkingCapitalLoanBreachScheduleApi workingCapitalLoanBreachSchedule() {
return create(WorkingCapitalLoanBreachScheduleApi.class);
}

public InternalWorkingCapitalLoansApi internalWorkingCapitalLoans() {
return create(InternalWorkingCapitalLoansApi.class);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* 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.test.stepdef.loan;

import static org.apache.fineract.client.feign.util.FeignCalls.ok;
import static org.assertj.core.api.Assertions.assertThat;

import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Then;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.models.PostWorkingCapitalLoansResponse;
import org.apache.fineract.client.models.WorkingCapitalLoanBreachScheduleData;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;

@Slf4j
@RequiredArgsConstructor
public class WorkingCapitalBreachScheduleStepDef extends AbstractStepDef {

private final FineractFeignClient fineractClient;

@Then("Working Capital loan breach schedule has no data")
public void verifyBreachScheduleIsEmpty() {
final Long loanId = extractLoanId();
final List<WorkingCapitalLoanBreachScheduleData> schedule = retrieveBreachSchedule(loanId);

assertThat(schedule).as("Breach schedule should be empty").isEmpty();

log.info("Verified that loan {} has no breach schedule", loanId);
}

@Then("Working Capital loan breach schedule has the following data:")
public void verifyBreachSchedule(final DataTable dataTable) {
final Long loanId = extractLoanId();
final List<WorkingCapitalLoanBreachScheduleData> schedule = retrieveBreachSchedule(loanId);

final List<List<String>> rows = dataTable.asLists();
final List<String> headers = rows.getFirst();
final List<List<String>> expectedData = rows.subList(1, rows.size());

assertThat(schedule).as("Breach schedule size should match expected data").hasSize(expectedData.size());

for (int i = 0; i < expectedData.size(); i++) {
final List<String> expectedRow = expectedData.get(i);
final WorkingCapitalLoanBreachScheduleData actualRow = schedule.get(i);

for (int j = 0; j < headers.size(); j++) {
final String header = headers.get(j);
final String expectedValue = expectedRow.get(j);
verifyField(actualRow, header, expectedValue, i + 1);
}
}

log.info("Successfully verified {} breach schedule entries for loan {}", schedule.size(), loanId);
}

private Long extractLoanId() {
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
return loanResponse.getLoanId();
}

private List<WorkingCapitalLoanBreachScheduleData> retrieveBreachSchedule(final Long loanId) {
final List<WorkingCapitalLoanBreachScheduleData> schedule = ok(
() -> fineractClient.workingCapitalLoanBreachSchedule().retrieveBreachSchedule(loanId));
log.debug("Breach schedule for loan {}: {}", loanId, schedule);
return schedule;
}

private void verifyField(final WorkingCapitalLoanBreachScheduleData actual, final String fieldName, final String expectedValue,
final int rowNumber) {
switch (fieldName) {
case "periodNumber" ->
assertThat(actual.getPeriodNumber()).as("Period number for row %d", rowNumber).isEqualTo(Integer.parseInt(expectedValue));
case "fromDate" ->
assertThat(actual.getFromDate()).as("From date for row %d", rowNumber).isEqualTo(LocalDate.parse(expectedValue));
case "toDate" -> assertThat(actual.getToDate()).as("To date for row %d", rowNumber).isEqualTo(LocalDate.parse(expectedValue));
case "numberOfDays" -> verifyNullableInteger(actual.getNumberOfDays(), expectedValue, "Number of days", rowNumber);
case "minPaymentAmount" ->
verifyNullableBigDecimal(actual.getMinPaymentAmount(), expectedValue, "Min payment amount", rowNumber);
case "outstandingAmount" ->
verifyNullableBigDecimal(actual.getOutstandingAmount(), expectedValue, "Outstanding amount", rowNumber);
case "nearBreach" -> verifyNullableBoolean(actual.getNearBreach(), expectedValue, "Near breach", rowNumber);
case "breach" -> verifyNullableBoolean(actual.getBreach(), expectedValue, "Breach", rowNumber);
default -> throw new IllegalArgumentException("Unknown field name: " + fieldName);
}
}

private void verifyNullableBoolean(final Boolean actualValue, final String expectedValue, final String fieldDescription,
final int rowNumber) {
if ("null".equals(expectedValue)) {
assertThat(actualValue).as("%s for row %d", fieldDescription, rowNumber).isNull();
} else {
assertThat(actualValue).as("%s for row %d", fieldDescription, rowNumber).isEqualTo(Boolean.parseBoolean(expectedValue));
}
}

private void verifyNullableBigDecimal(final BigDecimal actualValue, final String expectedValue, final String fieldDescription,
final int rowNumber) {
if ("null".equals(expectedValue)) {
assertThat(actualValue).as("%s for row %d", fieldDescription, rowNumber).isNull();
} else {
assertThat(actualValue).as("%s for row %d", fieldDescription, rowNumber).isEqualByComparingTo(new BigDecimal(expectedValue));
}
}

private void verifyNullableInteger(final Integer actualValue, final String expectedValue, final String fieldDescription,
final int rowNumber) {
if ("null".equals(expectedValue)) {
assertThat(actualValue).as("%s for row %d", fieldDescription, rowNumber).isNull();
} else {
assertThat(actualValue).as("%s for row %d", fieldDescription, rowNumber).isEqualTo(Integer.parseInt(expectedValue));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
import org.apache.fineract.test.factory.WorkingCapitalRequestFactory;
import org.apache.fineract.test.helper.ErrorMessageHelper;
import org.apache.fineract.test.helper.Utils;
import org.apache.fineract.test.messaging.event.EventCheckHelper;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;

Expand All @@ -79,14 +78,42 @@ public class WorkingCapitalLoanAccountStepDef extends AbstractStepDef {
private final WorkingCapitalLoanProductResolver workingCapitalLoanProductResolver;
private final WorkingCapitalLoanRequestFactory workingCapitalLoanRequestFactory;
private final WorkingCapitalRequestFactory workingCapitalProductRequestFactory;
private final EventCheckHelper eventCheckHelper;

@When("Admin creates a working capital loan with the following data:")
public void createWorkingCapitalLoan(final DataTable table) {
final List<List<String>> data = table.asLists();
createWorkingCapitalLoanAccount(data.get(1));
}

@When("Admin creates a working capital loan using created product with the following data:")
public void createWorkingCapitalLoanUsingCreatedProduct(final DataTable table) {
final List<List<String>> data = table.asLists();
final List<String> rawData = data.get(1);
final Long clientId = extractClientId();
final PostWorkingCapitalLoanProductsResponse productResponse = testContext()
.get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE);
final Long loanProductId = productResponse.getResourceId();

final String submittedOnDate = rawData.get(0);
final String expectedDisbursementDate = rawData.get(1);
final String principal = rawData.get(2);
final String totalPayment = rawData.get(3);
final String periodPaymentRate = rawData.get(4);
final String discount = rawData.get(5);

final PostWorkingCapitalLoansRequest loansRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoansRequest(clientId)
.productId(loanProductId).submittedOnDate(submittedOnDate).expectedDisbursementDate(expectedDisbursementDate)
.principalAmount(new BigDecimal(principal)).totalPayment(new BigDecimal(totalPayment))
.periodPaymentRate(new BigDecimal(periodPaymentRate))
.discount(discount != null && !discount.isEmpty() ? new BigDecimal(discount) : null);
testContext().set(TestContextKey.LOAN_CREATE_REQUEST, loansRequest);

final PostWorkingCapitalLoansResponse response = ok(
() -> fineractClient.workingCapitalLoans().submitWorkingCapitalLoanApplication(loansRequest));
testContext().set(TestContextKey.LOAN_CREATE_RESPONSE, response);
log.info("Working Capital Loan created with dynamic product ID: {}, Loan ID: {}", loanProductId, response.getLoanId());
}

@Then("Working capital loan creation was successful")
public void verifyWorkingCapitalLoanCreationSuccess() {
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdRequest;
import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdResponse;
import org.apache.fineract.client.models.StringEnumOptionData;
import org.apache.fineract.client.models.WorkingCapitalBreachRequest;
import org.apache.fineract.test.data.workingcapitalproduct.DefaultWorkingCapitalLoanProduct;
import org.apache.fineract.test.factory.LoanProductsRequestFactory;
import org.apache.fineract.test.factory.WorkingCapitalRequestFactory;
Expand Down Expand Up @@ -125,6 +126,54 @@ public void createWorkingCapitalLoanProductWithBreachId() {
checkWorkingCapitalLoanProductCreate();
}

@When("Admin creates a new Working Capital Loan Product with breachId and overrides enabled")
public void createWorkingCapitalLoanProductWithBreachIdAndOverrides() {
final CommandProcessingResult breachCreateResponse = ok(() -> fineractFeignClient.workingCapitalBreaches()
.createWorkingCapitalBreach(workingCapitalRequestFactory.defaultWorkingCapitalBreachRequest()));
final Long breachId = breachCreateResponse.getResourceId();
testContext().set(TestContextKey.WORKING_CAPITAL_BREACH_ID, breachId);

final String name = DefaultWorkingCapitalLoanProduct.WCLP.getName() + Utils.randomStringGenerator("_", 10);
final PostWorkingCapitalLoanProductsRequest request = workingCapitalRequestFactory
.defaultWorkingCapitalLoanProductAllowAttributesOverrideRequest() //
.name(name) //
.breachId(breachId);

final PostWorkingCapitalLoanProductsResponse response = createWorkingCapitalLoanProduct(request);
testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE, response);
testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST, request);
checkWorkingCapitalLoanProductCreate();
}

@When("Admin creates a Working Capital Loan Product with custom breach config and overrides enabled:")
public void createWorkingCapitalLoanProductWithCustomBreachConfig(final DataTable table) {
final Map<String, String> data = table.asMaps().get(0);

final WorkingCapitalBreachRequest breachRequest = new WorkingCapitalBreachRequest()
.breachFrequency(Integer.valueOf(data.get("breachFrequency"))).breachFrequencyType(data.get("breachFrequencyType"))
.breachAmountCalculationType(data.get("breachAmountCalculationType"))
.breachAmount(new BigDecimal(data.get("breachAmount")));
final CommandProcessingResult breachCreateResponse = ok(
() -> fineractFeignClient.workingCapitalBreaches().createWorkingCapitalBreach(breachRequest));
final Long breachId = breachCreateResponse.getResourceId();
testContext().set(TestContextKey.WORKING_CAPITAL_BREACH_ID, breachId);

final String graceDaysStr = data.get("delinquencyGraceDays");
final Integer graceDays = graceDaysStr != null && !graceDaysStr.isEmpty() ? Integer.valueOf(graceDaysStr) : null;

final String name = DefaultWorkingCapitalLoanProduct.WCLP.getName() + Utils.randomStringGenerator("_", 10);
final PostWorkingCapitalLoanProductsRequest request = workingCapitalRequestFactory
.defaultWorkingCapitalLoanProductAllowAttributesOverrideRequest() //
.name(name) //
.breachId(breachId) //
.delinquencyGraceDays(graceDays);

final PostWorkingCapitalLoanProductsResponse response = createWorkingCapitalLoanProduct(request);
testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE, response);
testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST, request);
checkWorkingCapitalLoanProductCreate();
}

@When("Admin creates a new Working Capital Loan Product with external-id")
public void createWorkingCapitalLoanProductWithExternalId() {
final String workingCapitalProductDefaultName = DefaultWorkingCapitalLoanProduct.WCLP.getName()
Expand Down
Loading
Loading