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 @@ -49,6 +49,11 @@ public void restoreChargeAccrualDateConfig() {
globalConfigurationHelper.setGlobalConfigValueString("charge-accrual-date", "due-date");
}

@After("@BusinessDateDisabledCheck")
public void restoreBusinessDateConfig() {
globalConfigurationHelper.enableGlobalConfiguration("enable-business-date", 0L);
}

@Given("Global configuration {string} is disabled")
public void disableGlobalConfiguration(String configKey) {
globalConfigurationHelper.disableGlobalConfiguration(configKey, 0L);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2460,6 +2460,17 @@ public void adminChecksWorkingCapitalPeriodPaymentRateChangesHistoryByExternalId
checkPeriodPaymentRateChangeHistory(data, rateChangesResponse, header, resourceId);
}

@Then("Working Capital Loan latest period payment rate change was submitted on the current system date")
public void latestPeriodPaymentRateChangeSubmittedOnSystemDate() {
final Long loanId = getCreatedLoanId();
final List<WorkingCapitalLoanPeriodPaymentRateChangeData> rateChanges = ok(
() -> fineractClient.workingCapitalLoans().getWorkingCapitalLoanRateChangeHistoryById(loanId));
final WorkingCapitalLoanPeriodPaymentRateChangeData latest = rateChanges.stream()//
.max(Comparator.comparing(WorkingCapitalLoanPeriodPaymentRateChangeData::getId))//
.orElseThrow(() -> new IllegalStateException(String.format("No rate change found on loan [%s]", loanId)));
assertThat(latest.getSubmittedOnDate()).as("submittedOnDate of latest rate change on loan %s", loanId).isEqualTo(Utils.now());
}

// ====================================
// Private Helper Methods
// ====================================
Expand Down Expand Up @@ -4135,6 +4146,8 @@ private List<String> fetchValuesOfRateChangesHistory(List<String> header,
: new Utils.DoubleFormatter(rateChangeData.getNewRate().doubleValue()).format());
case "Reversed" ->
actualValues.add(rateChangeData.getReversed() == null ? null : String.valueOf(rateChangeData.getReversed()));
case "Submitted On Date" -> actualValues
.add(rateChangeData.getSubmittedOnDate() == null ? null : FORMATTER.format(rateChangeData.getSubmittedOnDate()));
default -> throw new IllegalStateException(String.format("Header name %s cannot be found", headerName));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.math.BigDecimal;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
Expand All @@ -35,13 +37,16 @@
import org.apache.fineract.client.models.PostWorkingCapitalLoansLoanIdNearBreachActionsRequest;
import org.apache.fineract.client.models.PostWorkingCapitalLoansResponse;
import org.apache.fineract.client.models.WorkingCapitalLoanNearBreachActionData;
import org.apache.fineract.test.helper.Utils;
import org.apache.fineract.test.stepdef.AbstractStepDef;
import org.apache.fineract.test.support.TestContextKey;

@Slf4j
@RequiredArgsConstructor
public class WorkingCapitalNearBreachActionStepDef extends AbstractStepDef {

private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd MMMM yyyy");

private final FineractFeignClient fineractClient;

@When("Admin creates a near breach reschedule action with threshold {string} frequency {int} frequencyType {string}")
Expand Down Expand Up @@ -114,10 +119,25 @@ private void verifyActionField(final WorkingCapitalLoanNearBreachActionData actu
assertThat(actual.getFrequency()).as("Frequency for row %d", rowNumber).isEqualTo(Integer.parseInt(expectedValue));
case "frequencyType" ->
assertThat(actual.getFrequencyType()).as("FrequencyType for row %d", rowNumber).isEqualTo(expectedValue);
case "submittedOnDate" -> {
assertThat(actual.getSubmittedOnDate()).as("SubmittedOnDate for row %d", rowNumber).isNotNull();
assertThat(FORMATTER.format(actual.getSubmittedOnDate())).as("SubmittedOnDate for row %d", rowNumber)
.isEqualTo(expectedValue);
}
default -> throw new IllegalArgumentException("Unknown near breach action field: " + fieldName);
}
}

@Then("Latest near breach action was submitted on the current system date")
public void latestNearBreachActionSubmittedOnSystemDate() {
final Long loanId = extractLoanId();
final WorkingCapitalLoanNearBreachActionData latest = retrieveNearBreachActionHistory(loanId).stream()//
.max(Comparator.comparing(WorkingCapitalLoanNearBreachActionData::getId))//
.orElseThrow(() -> new IllegalStateException(String.format("No near breach action found on loan [%s]", loanId)));
assertThat(latest.getSubmittedOnDate()).as("submittedOnDate of latest near breach action on loan %d", loanId)
.isEqualTo(Utils.now());
}

private PostWorkingCapitalLoansLoanIdNearBreachActionsRequest buildRequest(final String threshold, final int frequency,
final String frequencyType) {
return new PostWorkingCapitalLoansLoanIdNearBreachActionsRequest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1194,3 +1194,49 @@ Feature: Working Capital Near Breach Evaluation
| 1 | 2026-01-01 | 2026-02-11 | 42 | 250.00 | 250.00 | true | null |
Then Admin closes the Working Capital loan with all obligations met with a full repayment on "06 February 2026"

@TestRailId:C98199
Scenario: Verify near breach action submitted on date follows the business date and stays immutable
Given Global configuration "enable-business-date" is enabled
When Admin sets the business date to "01 January 2026"
And Admin creates a client with random data
And Admin creates a Working Capital Loan Product with breach and near breach config and overrides enabled:
| breachFrequency | breachFrequencyType | breachAmountCalculationType | breachAmount | nearBreachFrequency | nearBreachFrequencyType | nearBreachThreshold | delinquencyGraceDays |
| 9 | DAYS | FLAT | 90 | 3 | DAYS | 33.33 | |
And Admin creates a working capital loan using created product with the following data:
| submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount |
| 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 0 |
And Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026"
When Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount
#--- submitted on date is stamped with the business date in force at creation ---#
When Admin sets the business date to "02 January 2026"
And Admin creates a near breach reschedule action with threshold "50" frequency 3 frequencyType "DAYS"
Then Near breach action history has the following data:
| action | threshold | frequency | frequencyType | submittedOnDate |
| RESCHEDULE | 50 | 3 | DAYS | 02 January 2026 |
#--- a later business date stamps only the new record, the earlier one is immutable ---#
When Admin sets the business date to "04 January 2026"
And Admin creates a near breach reschedule action with threshold "60" frequency 5 frequencyType "DAYS"
Then Near breach action history has the following data:
| action | threshold | frequency | frequencyType | submittedOnDate |
| RESCHEDULE | 60 | 5 | DAYS | 04 January 2026 |
| RESCHEDULE | 50 | 3 | DAYS | 02 January 2026 |
Then Admin closes the Working Capital loan with all obligations met with a full repayment on "04 January 2026"

@TestRailId:C98200 @BusinessDateDisabledCheck
Scenario: Verify near breach action submitted on date falls back to the system date when business date is disabled
Given Global configuration "enable-business-date" is enabled
When Admin sets the business date to "01 January 2026"
And Admin creates a client with random data
And Admin creates a Working Capital Loan Product with breach and near breach config and overrides enabled:
| breachFrequency | breachFrequencyType | breachAmountCalculationType | breachAmount | nearBreachFrequency | nearBreachFrequencyType | nearBreachThreshold | delinquencyGraceDays |
| 9 | DAYS | FLAT | 90 | 3 | DAYS | 33.33 | |
And Admin creates a working capital loan using created product with the following data:
| submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount |
| 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 0 |
And Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026"
When Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount
#--- with the config off the stamp must be the machine date, not the stored business date ---#
Given Global configuration "enable-business-date" is disabled
When Admin creates a near breach reschedule action with threshold "40" frequency 4 frequencyType "DAYS"
Then Latest near breach action was submitted on the current system date
Then Global configuration "enable-business-date" is enabled
Original file line number Diff line number Diff line change
Expand Up @@ -1956,3 +1956,46 @@ Feature: Working Capital Period Payment Rate
| product.name | submittedOnDate | expectedDisbursementDate | status | principal | approvedPrincipal | totalPaymentVolume | periodPaymentRate | discount |
| WCLP_ADVANCED_ACCOUNTING | 2026-01-01 | 2026-01-01 | Active | 1100.0 | 1000.0 | 100000.0 | 18.0 | 100.0 |
Then Admin closes the Working Capital loan with a full repayment on "01 February 2026"

@TestRailId:C98201
Scenario: Verify Working Capital period payment rate change submitted on date follows the business date and stays immutable - UC24
When Admin sets the business date to "01 January 2026"
And Admin creates a client with random data
And Admin creates a working capital loan with the following data:
| LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount |
| WCLP | 01 January 2026 | 01 January 2026 | 100 | 100 | 1 | 0 |
Then Working capital loan creation was successful
Then Admin successfully approves the working capital loan on "01 January 2026" with "100" amount and expected disbursement date on "01 January 2026"
Then Admin successfully disburse the Working Capital loan on "01 January 2026" with "100" EUR transaction amount
Then Working Capital loan status will be "ACTIVE"
#--- submitted on date is stamped with the business date in force at creation ---#
When Admin sets the business date to "10 January 2026"
And Admin update Working Capital period payment rate with "12.5" value
Then Working Capital Loan Period Payment Rate changes history contains the following data:
| Effective Date | Previous Rate | New Rate | Reversed | Submitted On Date |
| 10 January 2026 | 1.0 | 12.5 | false | 10 January 2026 |
#--- a later business date stamps only the new record, the earlier one is immutable ---#
When Admin sets the business date to "20 January 2026"
And Admin update Working Capital period payment rate with "15" value
Then Working Capital Loan Period Payment Rate changes history contains the following data:
| Effective Date | Previous Rate | New Rate | Reversed | Submitted On Date |
| 10 January 2026 | 1.0 | 12.5 | false | 10 January 2026 |
| 20 January 2026 | 12.5 | 15.0 | false | 20 January 2026 |
Then Admin closes the Working Capital loan with a full repayment on "20 January 2026"

@TestRailId:C98202 @BusinessDateDisabledCheck
Scenario: Verify Working Capital period payment rate change submitted on date falls back to the system date when business date is disabled - UC25
When Admin sets the business date to "01 January 2026"
And Admin creates a client with random data
And Admin creates a working capital loan with the following data:
| LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount |
| WCLP | 01 January 2026 | 01 January 2026 | 100 | 100 | 1 | 0 |
Then Working capital loan creation was successful
Then Admin successfully approves the working capital loan on "01 January 2026" with "100" amount and expected disbursement date on "01 January 2026"
Then Admin successfully disburse the Working Capital loan on "01 January 2026" with "100" EUR transaction amount
Then Working Capital loan status will be "ACTIVE"
#--- with the config off the stamp must be the machine date, not the stored business date ---#
Given Global configuration "enable-business-date" is disabled
When Admin update Working Capital period payment rate with "12.5" value
Then Working Capital Loan latest period payment rate change was submitted on the current system date
Then Global configuration "enable-business-date" is enabled
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
package org.apache.fineract.portfolio.workingcapitalloan.data;

import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.time.LocalDate;
import org.apache.fineract.portfolio.workingcapitalloan.domain.NearBreachActionType;

public record WorkingCapitalLoanNearBreachActionData(Long id, Long loanId, NearBreachActionType action, BigDecimal threshold,
Integer frequency, String frequencyType, OffsetDateTime createdDate) {
Integer frequency, String frequencyType, LocalDate submittedOnDate) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.OffsetDateTime;

public record WorkingCapitalLoanPeriodPaymentRateChangeData(Long id, Long loanId, LocalDate effectiveDate, BigDecimal previousRate,
BigDecimal newRate, boolean reversed, LocalDate reversedOnDate, OffsetDateTime createdDate) {
BigDecimal newRate, boolean reversed, LocalDate reversedOnDate, LocalDate submittedOnDate) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils;

@Getter
@Setter
Expand All @@ -57,6 +59,9 @@ public class WorkingCapitalLoanNearBreachAction extends AbstractAuditableWithUTC
@Column(name = "frequency_type")
private WorkingCapitalLoanPeriodFrequencyType frequencyType;

@Column(name = "submitted_on_date", nullable = false)
private LocalDate submittedOnDate;

public static WorkingCapitalLoanNearBreachAction create(final WorkingCapitalLoan loan, final NearBreachActionType action,
final BigDecimal threshold, final Integer frequency, final WorkingCapitalLoanPeriodFrequencyType frequencyType) {
final WorkingCapitalLoanNearBreachAction entity = new WorkingCapitalLoanNearBreachAction();
Expand All @@ -65,6 +70,7 @@ public static WorkingCapitalLoanNearBreachAction create(final WorkingCapitalLoan
entity.threshold = threshold;
entity.frequency = frequency;
entity.frequencyType = frequencyType;
entity.submittedOnDate = DateUtils.getBusinessLocalDate();
return entity;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils;

@Getter
@Setter
Expand Down Expand Up @@ -58,6 +59,9 @@ public class WorkingCapitalLoanPeriodPaymentRateChange extends AbstractAuditable
@Column(name = "reversed_on_date")
private LocalDate reversedOnDate;

@Column(name = "submitted_on_date", nullable = false)
private LocalDate submittedOnDate;

@Version
private int version;

Expand All @@ -69,6 +73,7 @@ public static WorkingCapitalLoanPeriodPaymentRateChange create(final WorkingCapi
change.previousRate = previousRate;
change.newRate = newRate;
change.reversed = false;
change.submittedOnDate = DateUtils.getBusinessLocalDate();
return change;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public interface WorkingCapitalLoanNearBreachActionMapper {

@Mapping(target = "loanId", source = "workingCapitalLoan.id")
@Mapping(target = "frequencyType", expression = "java(entity.getFrequencyType() != null ? entity.getFrequencyType().name() : null)")
@Mapping(target = "createdDate", expression = "java(entity.getCreatedDate().orElse(null))")
WorkingCapitalLoanNearBreachActionData toData(WorkingCapitalLoanNearBreachAction entity);

List<WorkingCapitalLoanNearBreachActionData> toDataList(List<WorkingCapitalLoanNearBreachAction> entities);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ private WorkingCapitalLoanPeriodPaymentRateChangeData toData(final WorkingCapita
final Long loanId) {
return new WorkingCapitalLoanPeriodPaymentRateChangeData(entity.getId(), loanId, entity.getEffectiveDate(),
entity.getPreviousRate(), entity.getNewRate(), entity.isReversed(), entity.getReversedOnDate(),
entity.getCreatedDate().orElse(null));
entity.getSubmittedOnDate());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,6 @@
<include relativeToChangelogFile="true" file="parts/0067_wc_loan_write_off.xml"/>
<include relativeToChangelogFile="true" file="parts/0068_wc_missed_payment_acknowledgement_business_step.xml"/>
<include relativeToChangelogFile="true" file="parts/0069_wc_business_events_extension.xml"/>
<include relativeToChangelogFile="true" file="parts/0070_wc_loan_period_payment_rate_change_submitted_on_date.xml"/>
<include relativeToChangelogFile="true" file="parts/0071_wc_loan_near_breach_action_submitted_on_date.xml"/>
</databaseChangeLog>
Loading
Loading