Skip to content
Merged
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 @@ -863,6 +863,15 @@ public CommandWrapperBuilder discountFeeAdjustmentWorkingCapitalLoanTransaction(
return this;
}

public CommandWrapperBuilder undoWorkingCapitalLoanTransaction(final Long loanId, final Long transactionId) {
this.actionName = ACTION_UNDO;
this.entityName = ENTITY_WORKINGCAPITALLOAN;
Comment thread
adamsaghy marked this conversation as resolved.
this.entityId = transactionId;
this.loanId = loanId;
this.href = "/working-capital-loans/" + loanId + "/transactions/" + transactionId + "?command=undo";
return this;
}

public CommandWrapperBuilder createWorkingCapitalLoanDelinquencyAction(final Long workingCapitalLoanId) {
this.actionName = "CREATE";
this.entityName = "WC_DELINQUENCY_ACTION";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1118,10 +1118,6 @@ public static String discountAdjustmentExceedFailure() {
return "Failed data validation due to: cannot.be.more.than.discount.fee.";
}

public static String discountAdjustmentBackdatedFailure() {
return "Failed data validation due to: backdated.not.allowed.";
}

public static String discountAdjustmentBeforeDiscountDateFailure() {
return "Failed data validation due to: cannot.be.before.discount.fee.date.";
}
Expand All @@ -1138,6 +1134,22 @@ public static String discountAdjustmentNotActiveLoanFailure() {
return "Failed data validation due to: adjustment.only.allowed.for.active.loan.";
}

public static String discountAdjustmentUndoAlreadyReversedFailure() {
return "Failed data validation due to: discount.adjustment.already.reversed.";
}

public static String discountAdjustmentUndoInvalidTypeFailure() {
return "Undo is not supported for transaction type";
}

public static String discountAdjustmentUndoTransactionNotFoundFailure() {
return "Working capital loan transaction not found";
}

public static String discountAdjustmentUndoNotActiveLoanFailure() {
return "Failed data validation due to: undo.discount.adjustment.only.allowed.for.active.loan.";
}

public static String nearBreachCannotEnableWithoutBreachFailure() {
return "Failed data validation due to: cannot.enable.near.breach.without.breach.";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -1514,6 +1515,141 @@ public void loadDiscountFeeTransactionFromLoanForAdjustment() {
testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_DISCOUNT_FEE_RESPONSE, synthetic);
}

@When("Admin undo the last Discount fee adjustment on Working Capital loan account")
public void undoLastDiscountFeeAdjustmentWCLoan() {
final Long loanId = getCreatedLoanId();
final GetWorkingCapitalLoanTransactionsResponse body = ok(
() -> fineractClient.workingCapitalLoanTransactions().retrieveWorkingCapitalLoanTransactionsById(loanId));
if (body.getContent() == null || body.getContent().isEmpty()) {
throw new IllegalStateException("No Working Capital Loan transactions found");
}
final GetWorkingCapitalLoanTransactionIdResponse adjustmentTxn = body.getContent().stream()
.filter(t -> t.getType() != null && "loanTransactionType.discountFeeAdjustment".equals(t.getType().getCode()))
.filter(t -> !Boolean.TRUE.equals(t.getReversed()))
.max(Comparator.comparing(GetWorkingCapitalLoanTransactionIdResponse::getId))
.orElseThrow(() -> new IllegalStateException("Active discount fee adjustment transaction not found on loan"));
final PostWorkingCapitalLoanTransactionsRequest request = workingCapitalProductRequestFactory
.defaultWorkingCapitalLoanRepaymentRequest();
ok(() -> fineractClient.workingCapitalLoanTransactions().executeWorkingCapitalLoanTransactionCommandById(loanId,
adjustmentTxn.getId(), "undo", request));
}

@When("Admin undo the Discount fee adjustment with {string} amount on Working Capital loan account")
public void undoDiscountFeeAdjustmentByAmountWCLoan(final String adjustmentAmount) {
final Long loanId = getCreatedLoanId();
final GetWorkingCapitalLoanTransactionsResponse body = ok(
() -> fineractClient.workingCapitalLoanTransactions().retrieveWorkingCapitalLoanTransactionsById(loanId));
if (body.getContent() == null || body.getContent().isEmpty()) {
throw new IllegalStateException("No Working Capital Loan transactions found");
}

final BigDecimal amount = new BigDecimal(adjustmentAmount);
final GetWorkingCapitalLoanTransactionIdResponse adjustmentTxn = body.getContent().stream().filter(t -> t.getType() != null)
.filter(t -> "loanTransactionType.discountFeeAdjustment".equals(t.getType().getCode()))
.filter(t -> !Boolean.TRUE.equals(t.getReversed())).filter(t -> t.getTransactionAmount() != null)
.filter(t -> t.getTransactionAmount().compareTo(amount) == 0)
.max(Comparator.comparing(GetWorkingCapitalLoanTransactionIdResponse::getId)).orElseThrow(() -> new IllegalStateException(
"Active discount fee adjustment transaction with amount " + adjustmentAmount + " not found on loan"));

final PostWorkingCapitalLoanTransactionsRequest request = workingCapitalProductRequestFactory
.defaultWorkingCapitalLoanRepaymentRequest();

ok(() -> fineractClient.workingCapitalLoanTransactions().executeWorkingCapitalLoanTransactionCommandById(loanId,
adjustmentTxn.getId(), "undo", request));
}

@Then("Undo the last Discount fee adjustment on Working Capital loan account failed due to already reversed transaction with status code {int}")
public void undoLastDiscountFeeAdjustmentAlreadyReversedFailure(final int expectedStatus) {
final Long loanId = getCreatedLoanId();
final GetWorkingCapitalLoanTransactionsResponse body = ok(
() -> fineractClient.workingCapitalLoanTransactions().retrieveWorkingCapitalLoanTransactionsById(loanId));
if (body.getContent() == null || body.getContent().isEmpty()) {
throw new IllegalStateException("No Working Capital Loan transactions found");
}

final GetWorkingCapitalLoanTransactionIdResponse adjustmentTxn = body.getContent().stream().filter(t -> t.getType() != null)
.filter(t -> "loanTransactionType.discountFeeAdjustment".equals(t.getType().getCode()))
.max(Comparator.comparing(GetWorkingCapitalLoanTransactionIdResponse::getId))
.orElseThrow(() -> new IllegalStateException("Discount fee adjustment transaction not found on loan"));

final PostWorkingCapitalLoanTransactionsRequest request = workingCapitalProductRequestFactory
.defaultWorkingCapitalLoanRepaymentRequest();

final String errorMessage = ErrorMessageHelper.discountAdjustmentUndoAlreadyReversedFailure();

final CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoanTransactions()
.executeWorkingCapitalLoanTransactionCommandById(loanId, adjustmentTxn.getId(), "undo", request));

assertThat(exception.getStatus()).as(errorMessage).isEqualTo(expectedStatus);

assertThat(exception.getDeveloperMessage()).contains(errorMessage);
}

@Then("Undo discount fee adjustment referencing the discount fee transaction on Working Capital loan account failed due to invalid transaction type with status code {int}")
public void undoDiscountFeeAdjustmentInvalidTypeFailure(final int expectedStatus) {
final Long loanId = getCreatedLoanId();
final PostWorkingCapitalLoanTransactionsResponse lastDiscountResponse = testContext()
.get(TestContextKey.WORKING_CAPITAL_LOAN_DISCOUNT_FEE_RESPONSE);

Assertions.assertNotNull(lastDiscountResponse);

final PostWorkingCapitalLoanTransactionsRequest request = workingCapitalProductRequestFactory
.defaultWorkingCapitalLoanRepaymentRequest();

final String errorMessage = ErrorMessageHelper.discountAdjustmentUndoInvalidTypeFailure();

final CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoanTransactions()
.executeWorkingCapitalLoanTransactionCommandById(loanId, lastDiscountResponse.getResourceId(), "undo", request));

assertThat(exception.getStatus()).as(errorMessage).isEqualTo(expectedStatus);

assertThat(exception.getDeveloperMessage()).contains(errorMessage);
}

@Then("Undo discount fee adjustment with a non-existent transaction id on Working Capital loan account failed as not found with status code {int}")
public void undoDiscountFeeAdjustmentNotFoundFailure(final int expectedStatus) {
final Long loanId = getCreatedLoanId();
final PostWorkingCapitalLoanTransactionsRequest request = workingCapitalProductRequestFactory
.defaultWorkingCapitalLoanRepaymentRequest();

final String errorMessage = ErrorMessageHelper.discountAdjustmentUndoTransactionNotFoundFailure();

final CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoanTransactions()
.executeWorkingCapitalLoanTransactionCommandById(loanId, 999999999L, "undo", request));

assertThat(exception.getStatus()).as(errorMessage).isEqualTo(expectedStatus);

assertThat(exception.getDeveloperMessage()).contains(errorMessage);
}

@Then("Undo the last Discount fee adjustment on Working Capital loan account failed due to non active loan with status code {int}")
public void undoLastDiscountFeeAdjustmentNotActiveLoanFailure(final int expectedStatus) {
final Long loanId = getCreatedLoanId();
final GetWorkingCapitalLoanTransactionsResponse body = ok(
() -> fineractClient.workingCapitalLoanTransactions().retrieveWorkingCapitalLoanTransactionsById(loanId));
if (body.getContent() == null || body.getContent().isEmpty()) {
throw new IllegalStateException("No Working Capital Loan transactions found");
}

final GetWorkingCapitalLoanTransactionIdResponse adjustmentTxn = body.getContent().stream().filter(t -> t.getType() != null)
.filter(t -> "loanTransactionType.discountFeeAdjustment".equals(t.getType().getCode()))
.filter(t -> !Boolean.TRUE.equals(t.getReversed()))
.max(Comparator.comparing(GetWorkingCapitalLoanTransactionIdResponse::getId))
.orElseThrow(() -> new IllegalStateException("Active discount fee adjustment transaction not found on loan"));

final PostWorkingCapitalLoanTransactionsRequest request = workingCapitalProductRequestFactory
.defaultWorkingCapitalLoanRepaymentRequest();

final String errorMessage = ErrorMessageHelper.discountAdjustmentUndoNotActiveLoanFailure();

final CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoanTransactions()
.executeWorkingCapitalLoanTransactionCommandById(loanId, adjustmentTxn.getId(), "undo", request));

assertThat(exception.getStatus()).as(errorMessage).isEqualTo(expectedStatus);

assertThat(exception.getDeveloperMessage()).contains(errorMessage);
}

@And("Add Discount fee adjustment with {string} amount on Working Capital loan account failed due to exceeding discount amount")
public void addDiscountFeeAdjustmentExceededFailure(final String adjustmentAmount) {
addDiscountFeeAdjustmentFailedCheck(adjustmentAmount, null, ErrorMessageHelper.discountAdjustmentExceedFailure());
Expand All @@ -1530,11 +1666,6 @@ public void addDiscountFeeAdjustmentFutureDateFailure(final String adjustmentAmo
addDiscountFeeAdjustmentFailedCheck(adjustmentAmount, transactionDate, ErrorMessageHelper.discountAdjustmentFutureDateFailure());
}

@Then("Add Discount fee adjustment with {string} amount and transaction date {string} on Working Capital loan account failed due to backdated transaction date")
public void addDiscountFeeAdjustmentBackdatedFailure(final String adjustmentAmount, final String transactionDate) {
addDiscountFeeAdjustmentFailedCheck(adjustmentAmount, transactionDate, ErrorMessageHelper.discountAdjustmentBackdatedFailure());
}

@Then("Add Discount fee adjustment with {string} amount and transaction date {string} on Working Capital loan account failed as amount must be greater then zero")
public void addDiscountFeeAdjustmentZeroAmountFailure(final String adjustmentAmount, final String transactionDate) {
addDiscountFeeAdjustmentFailedCheck(adjustmentAmount, transactionDate, ErrorMessageHelper.discountAdjustmentZeroAmountFailure());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4101,3 +4101,13 @@ Feature: LoanAccrualActivity - Part2
When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "21 July 2026" with 300 EUR transaction amount and system-generated Idempotency key
Then Loan status will be "OVERPAID"
Then LoanAccrualAdjustmentTransactionBusinessEvent is raised on "20 July 2026"
# post-due-date Accrual is PRESERVED (not bare-reversed)
Then Loan Transactions tab has a transaction with date: "20 July 2026", and with the following data:
| Transaction Type | Amount | Interest | Reverted |
| Accrual | 19.75 | 19.75 | false |
# ...and its effect is cancelled by a visible ACCRUAL_ADJUSTMENT (the fix), instead of a hidden reversal.
Then Loan Transactions tab has a transaction with date: "20 July 2026", and with the following data:
| Transaction Type | Amount | Interest | Reverted |
| Accrual Adjustment | 19.75 | 19.75 | false |
# Net recognised interest income is unchanged
Then Loan has 19.75 total Accruals
Original file line number Diff line number Diff line change
Expand Up @@ -80,28 +80,6 @@ Feature: Working Capital Discount Adjustment
| 01 January 2026 | Disbursement | 100.0 | 100.0 | 0.0 | 0.0 | false |
| 01 January 2026 | Discount Fee | 12.0 | 12.0 | 0.0 | 0.0 | false |

@TestRailId:C83028
Scenario: Verify Discount fee adjustment fails when transaction date is before business date - UC5
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 | totalPayment | periodPaymentRate | discount |
| WCLP | 01 January 2026 | 01 January 2026 | 100 | 100 | 1 | |
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 Admin adds Discount fee with "12" amount on Working Capital loan account for last disbursement
And Working Capital Loan has transactions:
| transactionDate | type | transactionAmount | principalPortion | feeChargesPortion | penaltyChargesPortion | reversed |
| 01 January 2026 | Disbursement | 100.0 | 100.0 | 0.0 | 0.0 | false |
| 01 January 2026 | Discount Fee | 12.0 | 12.0 | 0.0 | 0.0 | false |
When Admin sets the business date to "20 January 2026"
Then Add Discount fee adjustment with "2" amount and transaction date "15 January 2026" on Working Capital loan account failed due to backdated transaction date
And Working Capital Loan has transactions:
| transactionDate | type | transactionAmount | principalPortion | feeChargesPortion | penaltyChargesPortion | reversed |
| 01 January 2026 | Disbursement | 100.0 | 100.0 | 0.0 | 0.0 | false |
| 01 January 2026 | Discount Fee | 12.0 | 12.0 | 0.0 | 0.0 | false |

@TestRailId:C83029
Scenario: Verify Discount fee adjustment fails with transaction future date - UC6
When Admin sets the business date to "01 January 2026"
Expand Down
Loading