Skip to content

Commit e0d817a

Browse files
authored
Merge pull request #5753
FINERACT-2455: WC - Discount validation on approve/disburse if override disallowed
2 parents 77518f7 + 8307d30 commit e0d817a

File tree

5 files changed

+89
-3
lines changed

5 files changed

+89
-3
lines changed

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,23 @@ public void approveWorkingCapitalLoanWithExceededDiscountFailure(final String ap
507507
assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.discountAmountExceedFailure());
508508
}
509509

510+
@When("Admin failed to approve the working capital loan on {string} with {string} amount and expected disbursement date on {string} with {string} discount amount due to override disallowed by product")
511+
public void approveWorkingCapitalLoanWithDiscountOverrideDisallowedFailure(final String approveDate, final String approvedAmount,
512+
final String expectedDisbursementDate, final String discountAmount) {
513+
final PostWorkingCapitalLoansLoanIdRequest approveRequest = workingCapitalLoanRequestFactory
514+
.defaultWorkingCapitalLoanApproveRequest()//
515+
.approvedOnDate(approveDate)//
516+
.approvedLoanAmount(new BigDecimal(approvedAmount))//
517+
.discountAmount(new BigDecimal(discountAmount))//
518+
.expectedDisbursementDate(expectedDisbursementDate);//
519+
520+
final CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoans()
521+
.stateTransitionWorkingCapitalLoanById(getCreatedLoanId(), "approve", approveRequest));
522+
523+
assertThat(exception.getStatus()).as(ErrorMessageHelper.discountOverrideDisallowedByProductFailure()).isEqualTo(400);
524+
assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.discountOverrideDisallowedByProductFailure());
525+
}
526+
510527
@When("Admin rejects the working capital loan on {string}")
511528
public void rejectWorkingCapitalLoan(final String rejectDate) {
512529
final PostWorkingCapitalLoansLoanIdRequest rejectRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoanRejectRequest()
@@ -632,6 +649,20 @@ public void disburseWorkingCapitalLoanWithExceededDiscountFailure(String actualD
632649
assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.discountAmountExceedFailure());
633650
}
634651

652+
@When("Admin failed to disburse the working capital loan on {string} with {string} amount with {string} discount amount due to override disallowed by product")
653+
public void disburseWorkingCapitalLoanWithDiscountOverrideDisallowedFailure(final String actualDisbursementDate,
654+
final String transactionAmount, final String discountAmount) {
655+
final PostWorkingCapitalLoansLoanIdRequest disburseRequest = workingCapitalLoanRequestFactory
656+
.defaultWorkingCapitalLoanDisburseRequest().actualDisbursementDate(actualDisbursementDate)//
657+
.discountAmount(new BigDecimal(discountAmount)).transactionAmount(new BigDecimal(transactionAmount));
658+
659+
final CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoans()
660+
.stateTransitionWorkingCapitalLoanById(getCreatedLoanId(), "disburse", disburseRequest));
661+
662+
assertThat(exception.getStatus()).as(ErrorMessageHelper.discountOverrideDisallowedByProductFailure()).isEqualTo(400);
663+
assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.discountOverrideDisallowedByProductFailure());
664+
}
665+
635666
@Then("Verify Working Capital loan disbursement was successful")
636667
public void checkDisbursementData() {
637668
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);

fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalProductLoanAccount.feature

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,11 +1061,13 @@ Feature: WorkingCapitalLoanAccount
10611061
And Working capital loan account has the correct data:
10621062
| product.name | submittedOnDate | expectedDisbursementDate | status | principal | approvedPrincipal | totalPayment | periodPaymentRate | discount |
10631063
| WCLP_DISALLOW_ATTRIBUTES_OVERRIDE | 2026-01-01 | 2026-01-01 | Submitted and pending approval | 100.0 | 0.0 | 100.0 | 1.0 | null |
1064+
Then Admin failed to approve the working capital loan on "01 January 2026" with "100" amount and expected disbursement date on "01 January 2026" with "20" discount amount due to override disallowed by product
10641065
Then Admin successfully approves the working capital loan on "01 January 2026" with "100" amount and expected disbursement date on "01 January 2026"
10651066
Then Working capital loan approval was successful
10661067
And Working capital loan account has the correct data:
10671068
| product.name | submittedOnDate | expectedDisbursementDate | status | principal | approvedPrincipal | totalPayment | periodPaymentRate | discount |
10681069
| WCLP_DISALLOW_ATTRIBUTES_OVERRIDE | 2026-01-01 | 2026-01-01 | Approved | 100.0 | 100.0 | 100.0 | 1.0 | null |
1070+
Then Admin failed to disburse the working capital loan on "01 January 2026" with "100" amount with "20" discount amount due to override disallowed by product
10691071
Then Admin successfully disburse the Working Capital loan on "01 January 2026" with "100" EUR transaction amount
10701072
Then Working Capital loan status will be "ACTIVE"
10711073
Then Verify Working Capital loan disbursement was successful

fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/serialization/WorkingCapitalLoanDataValidator.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ public void validateApproval(final String json, final WorkingCapitalLoan loan) {
143143

144144
// discountAmount must be >= 0 and <= current (creation-time) discount
145145
if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanConstants.discountAmountParamName, element)) {
146+
if (isDiscountOverrideAllowed(loan)) {
147+
baseDataValidator.reset().parameter(WorkingCapitalLoanConstants.discountAmountParamName)
148+
.failWithCode("override.not.allowed.by.product");
149+
}
146150
final BigDecimal discountAmount = this.fromApiJsonHelper
147151
.extractBigDecimalNamed(WorkingCapitalLoanConstants.discountAmountParamName, element, new HashSet<>());
148152
baseDataValidator.reset().parameter(WorkingCapitalLoanConstants.discountAmountParamName).value(discountAmount).ignoreIfNull()
@@ -268,6 +272,10 @@ public void validateDisbursement(final String json, final WorkingCapitalLoan loa
268272
}
269273

270274
if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanConstants.discountAmountParamName, element)) {
275+
if (isDiscountOverrideAllowed(loan)) {
276+
baseDataValidator.reset().parameter(WorkingCapitalLoanConstants.discountAmountParamName)
277+
.failWithCode("override.not.allowed.by.product");
278+
}
271279
final BigDecimal discountAmount = this.fromApiJsonHelper
272280
.extractBigDecimalNamed(WorkingCapitalLoanConstants.discountAmountParamName, element, new HashSet<>());
273281
baseDataValidator.reset().parameter(WorkingCapitalLoanConstants.discountAmountParamName).value(discountAmount).ignoreIfNull()
@@ -366,9 +374,7 @@ public void validateUpdateDiscount(final String json, final WorkingCapitalLoan l
366374
.resource(WorkingCapitalLoanConstants.RESOURCE_NAME);
367375
final JsonElement element = this.fromApiJsonHelper.parse(json);
368376

369-
final boolean discountOverrideAllowed = loan.getLoanProduct() != null && loan.getLoanProduct().getConfigurableAttributes() != null
370-
&& loan.getLoanProduct().getConfigurableAttributes().isDiscountDefault();
371-
if (!discountOverrideAllowed) {
377+
if (isDiscountOverrideAllowed(loan)) {
372378
baseDataValidator.reset().parameter(WorkingCapitalLoanConstants.discountAmountParamName)
373379
.failWithCode("override.not.allowed.by.product");
374380
}
@@ -410,4 +416,9 @@ private void throwExceptionIfValidationWarningsExist(final List<ApiParameterErro
410416
throw new PlatformApiDataValidationException(dataValidationErrors);
411417
}
412418
}
419+
420+
private boolean isDiscountOverrideAllowed(final WorkingCapitalLoan loan) {
421+
return loan.getLoanProduct() == null || loan.getLoanProduct().getConfigurableAttributes() == null
422+
|| !loan.getLoanProduct().getConfigurableAttributes().isDiscountDefault();
423+
}
413424
}

integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApprovalRejectionTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import static org.junit.jupiter.api.Assertions.assertEquals;
2222
import static org.junit.jupiter.api.Assertions.assertNotNull;
23+
import static org.junit.jupiter.api.Assertions.assertTrue;
2324

2425
import com.google.gson.Gson;
2526
import com.google.gson.JsonObject;
@@ -360,6 +361,24 @@ public void testApproveWithDiscountExceedingCreatedValueFails() {
360361
productHelper.deleteWorkingCapitalLoanProductById(productId);
361362
}
362363

364+
@Test
365+
public void testApproveWithDiscountFailsWhenProductDisallowsDiscountOverride() {
366+
final Long productId = createProduct();
367+
final Long clientId = createClient();
368+
369+
final Long loanId = submitLoan(clientId, productId);
370+
371+
final CallFailedRuntimeException ex = applicationHelper.runApproveExpectingFailure(loanId, WorkingCapitalLoanApplicationTestBuilder
372+
.buildApproveJson(getSubmittedOnDate(loanId), BigDecimal.valueOf(5000), BigDecimal.valueOf(10)));
373+
assertNotNull(ex);
374+
assertEquals(400, ex.getStatus());
375+
assertNotNull(ex.getDeveloperMessage());
376+
assertTrue(ex.getDeveloperMessage().contains("override.not.allowed.by.product"));
377+
378+
applicationHelper.deleteById(loanId);
379+
productHelper.deleteWorkingCapitalLoanProductById(productId);
380+
}
381+
363382
// ========== External-ID endpoint tests ==========
364383

365384
@Test

integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanDisbursementTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,29 @@ public void testDisburseWithDiscountExceedingCreated() {
776776
assertTrue(ex.getDeveloperMessage().contains("discount") && ex.getDeveloperMessage().contains("exceed"));
777777
}
778778

779+
@Test
780+
public void testDisburseWithDiscountFailsWhenProductDisallowsDiscountOverride() {
781+
final Long productId = createProduct();
782+
783+
final BigDecimal approvedPrincipal = BigDecimal.valueOf(5000);
784+
final Long loanId = submitAndTrack(new WorkingCapitalLoanApplicationTestBuilder() //
785+
.withClientId(createdClientId) //
786+
.withProductId(productId) //
787+
.withPrincipal(approvedPrincipal) //
788+
.withPeriodPaymentRate(BigDecimal.ONE) //
789+
.buildSubmitJson());
790+
791+
applicationHelper.approveById(loanId,
792+
WorkingCapitalLoanApplicationTestBuilder.buildApproveJson(LocalDate.now(ZoneId.systemDefault()), approvedPrincipal, null));
793+
794+
final String disburseJson = WorkingCapitalLoanDisbursementTestBuilder.buildDisburseJson(LocalDate.now(ZoneId.systemDefault()),
795+
approvedPrincipal, BigDecimal.valueOf(10), null, null, null, null, null, null, null);
796+
final CallFailedRuntimeException ex = applicationHelper.runDisburseExpectingFailure(loanId, disburseJson);
797+
assertEquals(400, ex.getStatus());
798+
assertNotNull(ex.getDeveloperMessage());
799+
assertTrue(ex.getDeveloperMessage().contains("override.not.allowed.by.product"));
800+
}
801+
779802
@Test
780803
public void testDisburseWithDuplicateTransactionExternalId() {
781804
final Long productId = createProduct();

0 commit comments

Comments
 (0)