diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java index 6fe8d10d47c..36be7b7ca3f 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java @@ -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); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java index 5a67253bca8..74f595e26dd 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java @@ -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 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 // ==================================== @@ -4135,6 +4146,8 @@ private List fetchValuesOfRateChangesHistory(List 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)); } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalNearBreachActionStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalNearBreachActionStepDef.java index a87136fb963..0451df2451b 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalNearBreachActionStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalNearBreachActionStepDef.java @@ -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; @@ -35,6 +37,7 @@ 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; @@ -42,6 +45,8 @@ @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}") @@ -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() diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalNearBreachEvaluation.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalNearBreachEvaluation.feature index 96e8c4c49da..549219a66cf 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalNearBreachEvaluation.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalNearBreachEvaluation.feature @@ -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 diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalPeriodPaymentRate.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalPeriodPaymentRate.feature index 7eaa24c8c2b..b96853a6ceb 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalPeriodPaymentRate.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalPeriodPaymentRate.feature @@ -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 diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanNearBreachActionData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanNearBreachActionData.java index 26f5cbf2260..2a5e993827e 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanNearBreachActionData.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanNearBreachActionData.java @@ -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) { } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanPeriodPaymentRateChangeData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanPeriodPaymentRateChangeData.java index 1046c9f84c4..860d3f2fafa 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanPeriodPaymentRateChangeData.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanPeriodPaymentRateChangeData.java @@ -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) { } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanNearBreachAction.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanNearBreachAction.java index 76f94347920..e1700da6366 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanNearBreachAction.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanNearBreachAction.java @@ -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 @@ -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(); @@ -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; } } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanPeriodPaymentRateChange.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanPeriodPaymentRateChange.java index 71957075f81..8f53a4eb1d2 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanPeriodPaymentRateChange.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanPeriodPaymentRateChange.java @@ -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 @@ -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; @@ -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; } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanNearBreachActionMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanNearBreachActionMapper.java index 599ab6436b9..25063373905 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanNearBreachActionMapper.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanNearBreachActionMapper.java @@ -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 toDataList(List entities); diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanPeriodPaymentRateChangeReadServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanPeriodPaymentRateChangeReadServiceImpl.java index 3f1372536e1..6f476dd699f 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanPeriodPaymentRateChangeReadServiceImpl.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanPeriodPaymentRateChangeReadServiceImpl.java @@ -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()); } } diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml index e9ff8b41152..4cef02d7853 100644 --- a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml @@ -91,4 +91,6 @@ + + diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0070_wc_loan_period_payment_rate_change_submitted_on_date.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0070_wc_loan_period_payment_rate_change_submitted_on_date.xml new file mode 100644 index 00000000000..59b1e2de4d5 --- /dev/null +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0070_wc_loan_period_payment_rate_change_submitted_on_date.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + submitted_on_date IS NULL + + + + + + + + + + + diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0071_wc_loan_near_breach_action_submitted_on_date.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0071_wc_loan_near_breach_action_submitted_on_date.xml new file mode 100644 index 00000000000..e9a43ec0fa3 --- /dev/null +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0071_wc_loan_near_breach_action_submitted_on_date.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + submitted_on_date IS NULL + + + + + + + + + + + diff --git a/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalSubmittedOnDateTest.java b/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalSubmittedOnDateTest.java new file mode 100644 index 00000000000..b0bd7ec91aa --- /dev/null +++ b/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalSubmittedOnDateTest.java @@ -0,0 +1,147 @@ +/** + * 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.workingcapitalloan.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import org.apache.fineract.infrastructure.businessdate.data.service.BusinessDateDTO; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDate; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateRepository; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.businessdate.mapper.BusinessDateMapper; +import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformServiceImpl; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.domain.ActionContext; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class WorkingCapitalSubmittedOnDateTest { + + private static final LocalDate CONFIGURED_BUSINESS_DATE = LocalDate.of(2019, 3, 14); + private static final LocalDate RATE_EFFECTIVE_DATE = LocalDate.of(2018, 6, 1); + + @Mock + private ConfigurationDomainService configurationDomainService; + + @Mock + private BusinessDateRepository repository; + + @Mock + private BusinessDateMapper businessDateMapper; + + @InjectMocks + private BusinessDateReadPlatformServiceImpl businessDateReadPlatformService; + + @BeforeEach + void setUp() { + ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "UTC", null)); + ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); + } + + @AfterEach + void tearDown() { + ThreadLocalContextUtil.reset(); + } + + @Test + void periodPaymentRateChangeSubmittedOnDateIsConfiguredDateWhenBusinessDateEnabled() { + givenBusinessDateEnabled(CONFIGURED_BUSINESS_DATE); + loadResolvedDatesOntoRequestContext(); + + final WorkingCapitalLoanPeriodPaymentRateChange change = createPeriodPaymentRateChange(); + + assertThat(change.getSubmittedOnDate()).isEqualTo(CONFIGURED_BUSINESS_DATE); + assertThat(change.getSubmittedOnDate()).isNotEqualTo(DateUtils.getLocalDateOfTenant()); + } + + @Test + void periodPaymentRateChangeSubmittedOnDateIsTenantDateWhenBusinessDateDisabled() { + givenBusinessDateDisabled(); + loadResolvedDatesOntoRequestContext(); + + final WorkingCapitalLoanPeriodPaymentRateChange change = createPeriodPaymentRateChange(); + + assertThat(change.getSubmittedOnDate()).isEqualTo(DateUtils.getLocalDateOfTenant()); + verify(repository, never()).findAllBusinessDates(); + } + + @Test + void nearBreachActionSubmittedOnDateIsConfiguredDateWhenBusinessDateEnabled() { + givenBusinessDateEnabled(CONFIGURED_BUSINESS_DATE); + loadResolvedDatesOntoRequestContext(); + + final WorkingCapitalLoanNearBreachAction action = createNearBreachAction(); + + assertThat(action.getSubmittedOnDate()).isEqualTo(CONFIGURED_BUSINESS_DATE); + assertThat(action.getSubmittedOnDate()).isNotEqualTo(DateUtils.getLocalDateOfTenant()); + } + + @Test + void nearBreachActionSubmittedOnDateIsTenantDateWhenBusinessDateDisabled() { + givenBusinessDateDisabled(); + loadResolvedDatesOntoRequestContext(); + + final WorkingCapitalLoanNearBreachAction action = createNearBreachAction(); + + assertThat(action.getSubmittedOnDate()).isEqualTo(DateUtils.getLocalDateOfTenant()); + verify(repository, never()).findAllBusinessDates(); + } + + private void givenBusinessDateEnabled(final LocalDate configuredBusinessDate) { + final List stored = List.of(mock(BusinessDate.class)); + given(configurationDomainService.isBusinessDateEnabled()).willReturn(true); + given(repository.findAllBusinessDates()).willReturn(stored); + given(businessDateMapper.mapEntity(stored)) + .willReturn(List.of(BusinessDateDTO.builder().type(BusinessDateType.BUSINESS_DATE).date(configuredBusinessDate).build())); + } + + private void givenBusinessDateDisabled() { + given(configurationDomainService.isBusinessDateEnabled()).willReturn(false); + } + + private void loadResolvedDatesOntoRequestContext() { + ThreadLocalContextUtil.setBusinessDates(businessDateReadPlatformService.getBusinessDates()); + } + + private static WorkingCapitalLoanPeriodPaymentRateChange createPeriodPaymentRateChange() { + return WorkingCapitalLoanPeriodPaymentRateChange.create(mock(WorkingCapitalLoan.class), RATE_EFFECTIVE_DATE, BigDecimal.ONE, + new BigDecimal("12.5")); + } + + private static WorkingCapitalLoanNearBreachAction createNearBreachAction() { + return WorkingCapitalLoanNearBreachAction.create(mock(WorkingCapitalLoan.class), NearBreachActionType.RESCHEDULE, + new BigDecimal("10"), 7, WorkingCapitalLoanPeriodFrequencyType.DAYS); + } +}