Skip to content

Commit e5c0dff

Browse files
authored
Merge pull request #5680
FINERACT-2455: WC - Discount
2 parents 83fcc5c + 8cea388 commit e5c0dff

24 files changed

Lines changed: 1287 additions & 240 deletions

File tree

fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,14 @@ public CommandWrapperBuilder createWorkingCapitalLoanDelinquencyAction(final Lon
638638
return this;
639639
}
640640

641+
public CommandWrapperBuilder updateDiscountWorkingCapitalLoanApplication(final Long loanId) {
642+
this.actionName = "UPDATEDISCOUNT";
643+
this.entityName = "WORKINGCAPITALLOAN";
644+
this.entityId = loanId;
645+
this.href = "/workingcapitalloans/" + loanId;
646+
return this;
647+
}
648+
641649
public CommandWrapperBuilder createClientIdentifier(final Long clientId) {
642650
this.actionName = "CREATE";
643651
this.entityName = "CLIENTIDENTIFIER";

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/DefaultWorkingCapitalLoanProduct.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public enum DefaultWorkingCapitalLoanProduct implements WorkingCapitalLoanProduc
2222

2323
WCLP, //
2424
WCLP_DISALLOW_ATTRIBUTES_OVERRIDE, //
25+
WCLP_DISCOUNT, //
26+
WCLP_DISCOUNT_DISALLOW_ATTRIBUTES_OVERRIDE, //
2527
WCLP_FOR_UPDATE; //
2628

2729
@Override

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalLoanRequestFactory.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.fineract.client.models.PostWorkingCapitalLoansDelinquencyActionRequest;
2525
import org.apache.fineract.client.models.PostWorkingCapitalLoansLoanIdRequest;
2626
import org.apache.fineract.client.models.PostWorkingCapitalLoansRequest;
27+
import org.apache.fineract.client.models.PutWorkingCapitalLoansLoanIdDiscountRequest;
2728
import org.apache.fineract.client.models.PutWorkingCapitalLoansLoanIdRequest;
2829
import org.apache.fineract.test.data.workingcapitalproduct.DefaultWorkingCapitalLoanProduct;
2930
import org.apache.fineract.test.data.workingcapitalproduct.WorkingCapitalLoanProductResolver;
@@ -40,9 +41,10 @@ public class WorkingCapitalLoanRequestFactory {
4041
public static final String DEFAULT_LOCALE = "en";
4142
public static final DefaultWorkingCapitalLoanProduct DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT = DefaultWorkingCapitalLoanProduct.WCLP;
4243
public static final BigDecimal DEFAULT_PRINCIPAL = new BigDecimal(100);
44+
public static final BigDecimal DEFAULT_DISCOUNT = new BigDecimal(100);
4345
public static final BigDecimal DEFAULT_TOTAL_PAYMENT = new BigDecimal(100);
4446
public static final BigDecimal DEFAULT_PERIOD_PAYMENT_RATE = new BigDecimal(1);
45-
public static final BigDecimal DEFAULT_DISCOUNT = BigDecimal.ZERO;
47+
public static final BigDecimal DEFAULT_DISCOUNT_ZERO = BigDecimal.ZERO;
4648

4749
public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT);
4850
public static final String DATE_SUBMIT_STRING = FORMATTER.format(Utils.now().minusMonths(1L));
@@ -56,7 +58,7 @@ public PostWorkingCapitalLoansRequest defaultWorkingCapitalLoansRequest(Long cli
5658
.principalAmount(DEFAULT_PRINCIPAL)//
5759
.totalPayment(DEFAULT_TOTAL_PAYMENT)//
5860
.periodPaymentRate(DEFAULT_PERIOD_PAYMENT_RATE)//
59-
.discount(DEFAULT_DISCOUNT)//
61+
.discount(DEFAULT_DISCOUNT_ZERO)//
6062
.locale(DEFAULT_LOCALE)//
6163
.dateFormat(DATE_FORMAT);//
6264
}
@@ -112,4 +114,11 @@ public PostWorkingCapitalLoansDelinquencyActionRequest defaultWorkingCapitalLoan
112114
.dateFormat(DATE_FORMAT)//
113115
.locale(DEFAULT_LOCALE);//
114116
}
117+
118+
public PutWorkingCapitalLoansLoanIdDiscountRequest defaultWorkingCapitalLoanUpdateDiscountRequest() {
119+
return new PutWorkingCapitalLoansLoanIdDiscountRequest()//
120+
.discountAmount(DEFAULT_DISCOUNT).note("")//
121+
.dateFormat(DATE_FORMAT)//
122+
.locale(DEFAULT_LOCALE);//
123+
}
115124
}

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalRequestFactory.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,13 @@ public PutWorkingCapitalLoanProductsProductIdRequest defaultWorkingCapitalLoanPr
131131
.principal(new BigDecimal(200))//
132132
.minPrincipal(new BigDecimal(15))//
133133
.maxPrincipal(new BigDecimal(300000))//
134-
.amortizationType(PutWorkingCapitalLoanProductsProductIdRequest.AmortizationTypeEnum.EIR)//
134+
.discount(new BigDecimal(50)).amortizationType(PutWorkingCapitalLoanProductsProductIdRequest.AmortizationTypeEnum.EIR)//
135135
.npvDayCount(DAYS365.value)//
136136
.delinquencyBucketId(null)//
137137
.dateFormat(DATE_FORMAT)//
138138
.locale(LOCALE_EN)//
139-
.allowAttributeOverrides(allowAttributeOverrides).paymentAllocation(List.of(//
139+
.allowAttributeOverrides(allowAttributeOverrides)//
140+
.paymentAllocation(List.of(//
140141
createPaymentAllocation(PostPaymentAllocation.TransactionTypeEnum.DEFAULT.getValue(), //
141142
List.of(FEE, PRINCIPAL, PENALTY))));//
142143
}

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,4 +1072,20 @@ public static String disburseDateFailure(String errorMessageDescription) {
10721072
public static String undoDisbursalDisallowedFailure(String status) {
10731073
return String.format("Transition LOAN_DISBURSAL_UNDO is not allowed from status %s", status);
10741074
}
1075+
1076+
public static String discountAmountExceedFailure() {
1077+
return "Failed data validation due to: amount.cannot.exceed.created.discount.";
1078+
}
1079+
1080+
public static String discountAlreadySetBeforeDisburseFailure() {
1081+
return "Discount was already set before disbursement and cannot be added again";
1082+
}
1083+
1084+
public static String discountDiffDateFromDisburseFailure() {
1085+
return "Failed data validation due to: transaction.date.must.be.equal.disbursement.date.";
1086+
}
1087+
1088+
public static String discountOverrideDisallowedByProductFailure() {
1089+
return "Failed data validation due to: override.not.allowed.by.product.";
1090+
}
10751091
}

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

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.apache.fineract.client.models.PostWorkingCapitalLoansLoanIdResponse;
5454
import org.apache.fineract.client.models.PostWorkingCapitalLoansRequest;
5555
import org.apache.fineract.client.models.PostWorkingCapitalLoansResponse;
56+
import org.apache.fineract.client.models.PutWorkingCapitalLoansLoanIdDiscountRequest;
5657
import org.apache.fineract.client.models.PutWorkingCapitalLoansLoanIdRequest;
5758
import org.apache.fineract.client.models.PutWorkingCapitalLoansLoanIdResponse;
5859
import org.apache.fineract.test.data.LoanStatus;
@@ -437,6 +438,7 @@ public void approveWorkingCapitalLoan(final String approveDate, final String app
437438
final PostWorkingCapitalLoansLoanIdRequest approveRequest = workingCapitalLoanRequestFactory
438439
.defaultWorkingCapitalLoanApproveRequest().approvedOnDate(approveDate).approvedLoanAmount(new BigDecimal(approvedAmount))
439440
.expectedDisbursementDate(expectedDisbursementDate);
441+
testContext().set(TestContextKey.LOAN_APPROVAL_REQUEST, approveRequest);
440442

441443
executeStateTransition("approve", approveRequest, TestContextKey.LOAN_APPROVAL_RESPONSE, false);
442444
}
@@ -447,10 +449,25 @@ public void approveWorkingCapitalLoanByExternalId(final String approveDate, fina
447449
final PostWorkingCapitalLoansLoanIdRequest approveRequest = workingCapitalLoanRequestFactory
448450
.defaultWorkingCapitalLoanApproveRequest().approvedOnDate(approveDate).approvedLoanAmount(new BigDecimal(approvedAmount))
449451
.expectedDisbursementDate(expectedDisbursementDate);
452+
testContext().set(TestContextKey.LOAN_APPROVAL_REQUEST, approveRequest);
450453

451454
executeStateTransition("approve", approveRequest, TestContextKey.LOAN_APPROVAL_RESPONSE, true);
452455
}
453456

457+
@When("Admin successfully approves the working capital loan on {string} with {string} amount and {string} discount amount and expected disbursement date on {string}")
458+
public void approveWorkingCapitalLoanWithDiscount(final String approveDate, final String approvedAmount, final String discountAmount,
459+
final String expectedDisbursementDate) {
460+
final PostWorkingCapitalLoansLoanIdRequest approveRequest = workingCapitalLoanRequestFactory
461+
.defaultWorkingCapitalLoanApproveRequest()//
462+
.approvedOnDate(approveDate)//
463+
.approvedLoanAmount(new BigDecimal(approvedAmount))//
464+
.discountAmount(new BigDecimal(discountAmount))//
465+
.expectedDisbursementDate(expectedDisbursementDate);
466+
testContext().set(TestContextKey.LOAN_APPROVAL_REQUEST, approveRequest);
467+
468+
executeStateTransition("approve", approveRequest, TestContextKey.LOAN_APPROVAL_RESPONSE, false);
469+
}
470+
454471
@Then("Working capital loan approval was successful")
455472
public void verifyWorkingCapitalLoanApprovalSuccess() {
456473
verifyStateTransitionSuccess(TestContextKey.LOAN_APPROVAL_RESPONSE, "approval");
@@ -460,8 +477,10 @@ public void verifyWorkingCapitalLoanApprovalSuccess() {
460477
public void approvalOfWorkingCapitalLoanResultsAnError(final String approveDate, final String approvedAmount,
461478
final String expectedDisbursementDate, final DataTable table) {
462479
final PostWorkingCapitalLoansLoanIdRequest approveRequest = workingCapitalLoanRequestFactory
463-
.defaultWorkingCapitalLoanApproveRequest().approvedOnDate(approveDate).approvedLoanAmount(new BigDecimal(approvedAmount))
464-
.expectedDisbursementDate(expectedDisbursementDate);
480+
.defaultWorkingCapitalLoanApproveRequest()//
481+
.approvedOnDate(approveDate)//
482+
.approvedLoanAmount(new BigDecimal(approvedAmount))//
483+
.expectedDisbursementDate(expectedDisbursementDate);//
465484

466485
final CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoans()
467486
.stateTransitionWorkingCapitalLoanById(getCreatedLoanId(), "approve", approveRequest));
@@ -470,6 +489,23 @@ public void approvalOfWorkingCapitalLoanResultsAnError(final String approveDate,
470489
log.info("Verified working capital loan approval failed with expected error");
471490
}
472491

492+
@When("Admin failed to approve the working capital loan on {string} with {string} amount and expected disbursement date on {string} with {string} exceeded discount amount")
493+
public void approveWorkingCapitalLoanWithExceededDiscountFailure(final String approveDate, final String approvedAmount,
494+
final String expectedDisbursementDate, final String discountAmount) {
495+
final PostWorkingCapitalLoansLoanIdRequest approveRequest = workingCapitalLoanRequestFactory
496+
.defaultWorkingCapitalLoanApproveRequest()//
497+
.approvedOnDate(approveDate)//
498+
.approvedLoanAmount(new BigDecimal(approvedAmount))//
499+
.discountAmount(new BigDecimal(discountAmount))//
500+
.expectedDisbursementDate(expectedDisbursementDate);//
501+
502+
final CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoans()
503+
.stateTransitionWorkingCapitalLoanById(getCreatedLoanId(), "approve", approveRequest));
504+
505+
assertThat(exception.getStatus()).as(ErrorMessageHelper.discountAmountExceedFailure()).isEqualTo(400);
506+
assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.discountAmountExceedFailure());
507+
}
508+
473509
@When("Admin rejects the working capital loan on {string}")
474510
public void rejectWorkingCapitalLoan(final String rejectDate) {
475511
final PostWorkingCapitalLoansLoanIdRequest rejectRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoanRejectRequest()
@@ -533,6 +569,7 @@ public void disburseWCLoan(String actualDisbursementDate, String transactionAmou
533569
PostWorkingCapitalLoansLoanIdRequest disburseRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoanDisburseRequest()
534570
.actualDisbursementDate(actualDisbursementDate)//
535571
.transactionAmount(new BigDecimal(transactionAmount));
572+
testContext().set(TestContextKey.LOAN_DISBURSE_REQUEST, disburseRequest);
536573

537574
executeStateTransition("disburse", disburseRequest, TestContextKey.LOAN_DISBURSE_RESPONSE, false);
538575
verifyStateTransitionSuccess(TestContextKey.LOAN_DISBURSE_RESPONSE, "disbursement");
@@ -544,6 +581,7 @@ public void disburseWCLoanByExternalId(String actualDisbursementDate, String tra
544581
PostWorkingCapitalLoansLoanIdRequest disburseRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoanDisburseRequest()
545582
.actualDisbursementDate(actualDisbursementDate)//
546583
.transactionAmount(new BigDecimal(transactionAmount));
584+
testContext().set(TestContextKey.LOAN_DISBURSE_REQUEST, disburseRequest);
547585

548586
executeStateTransition("disburse", disburseRequest, TestContextKey.LOAN_DISBURSE_RESPONSE, true);
549587
verifyStateTransitionSuccess(TestContextKey.LOAN_DISBURSE_RESPONSE, "disbursement");
@@ -567,6 +605,51 @@ public void checkDisbursementData(String actualDisbursementDate, String transact
567605
assertThat(disbursementDetails.getActualAmount().compareTo(new BigDecimal(transactionAmount))).isEqualTo(0);
568606
}
569607

608+
@And("Admin successfully disburse the Working Capital loan on {string} with {string} EUR transaction amount and {string} discount amount")
609+
public void disburseWCLoanWithDiscount(String actualDisbursementDate, String transactionAmount, String discountAmount) {
610+
PostWorkingCapitalLoansLoanIdRequest disburseRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoanDisburseRequest()
611+
.actualDisbursementDate(actualDisbursementDate)//
612+
.discountAmount(new BigDecimal(discountAmount)).transactionAmount(new BigDecimal(transactionAmount));
613+
testContext().set(TestContextKey.LOAN_DISBURSE_REQUEST, disburseRequest);
614+
615+
executeStateTransition("disburse", disburseRequest, TestContextKey.LOAN_DISBURSE_RESPONSE, false);
616+
verifyStateTransitionSuccess(TestContextKey.LOAN_DISBURSE_RESPONSE, "disbursement");
617+
checkChangesExpectedStatus(TestContextKey.LOAN_DISBURSE_RESPONSE, ACTIVE);
618+
}
619+
620+
@When("Admin failed to disburse the working capital loan on {string} with {string} amount with {string} exceeded discount amount")
621+
public void disburseWorkingCapitalLoanWithExceededDiscountFailure(String actualDisbursementDate, String transactionAmount,
622+
String discountAmount) {
623+
PostWorkingCapitalLoansLoanIdRequest disburseRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoanDisburseRequest()
624+
.actualDisbursementDate(actualDisbursementDate)//
625+
.discountAmount(new BigDecimal(discountAmount)).transactionAmount(new BigDecimal(transactionAmount));
626+
627+
final CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoans()
628+
.stateTransitionWorkingCapitalLoanById(getCreatedLoanId(), "disburse", disburseRequest));
629+
630+
assertThat(exception.getStatus()).as(ErrorMessageHelper.discountAmountExceedFailure()).isEqualTo(400);
631+
assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.discountAmountExceedFailure());
632+
}
633+
634+
@Then("Verify Working Capital loan disbursement was successful")
635+
public void checkDisbursementData() {
636+
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
637+
long loanId = loanResponse.getLoanId();
638+
639+
GetWorkingCapitalLoansLoanIdResponse loanDetailsResponse = ok(
640+
() -> fineractClient.workingCapitalLoans().retrieveWorkingCapitalLoanById(loanId));
641+
String getLoanStatus = loanDetailsResponse.getStatus().getValue();
642+
assertThat(getLoanStatus.toUpperCase()).isEqualTo(ACTIVE.name());
643+
644+
PostWorkingCapitalLoansLoanIdRequest disburseLoanRequest = testContext().get(TestContextKey.LOAN_DISBURSE_REQUEST);
645+
646+
GetDisbursementDetail disbursementDetails = loanDetailsResponse.getDisbursementDetails().stream().findFirst()
647+
.orElseThrow(() -> new RuntimeException(""));
648+
String formattedDate = disbursementDetails.getActualDisbursementDate().format(FORMATTER);
649+
assertThat(formattedDate).isEqualTo(disburseLoanRequest.getActualDisbursementDate());
650+
assertThat(disbursementDetails.getActualAmount().compareTo(disburseLoanRequest.getTransactionAmount())).isEqualTo(0);
651+
}
652+
570653
@Then("Admin successfully undo Working Capital disbursal")
571654
public void undoDisbursalWCLoan() {
572655
PostWorkingCapitalLoansLoanIdRequest undoDisbursalRequest = workingCapitalLoanRequestFactory
@@ -627,6 +710,38 @@ public void undoDisbursalWCLoanFailure(String actualLoanStatus) {
627710
assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.undoDisbursalDisallowedFailure(actualLoanStatus));
628711
}
629712

713+
@And("Admin successfully update discount with {string} amount on Working Capital loan account")
714+
public void updateDiscountWCLoan(String discountAmount) {
715+
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
716+
long loanId = loanResponse.getLoanId();
717+
718+
PutWorkingCapitalLoansLoanIdDiscountRequest updateDiscountRequest = workingCapitalLoanRequestFactory
719+
.defaultWorkingCapitalLoanUpdateDiscountRequest().discountAmount(new BigDecimal(discountAmount));
720+
721+
PutWorkingCapitalLoansLoanIdResponse updateDiscountResponse = ok(
722+
() -> fineractClient.workingCapitalLoans().updateWorkingCapitalLoanDiscountById(loanId, updateDiscountRequest));
723+
724+
log.info("Working Capital Loan discount updated with ID: {}", updateDiscountResponse.getResourceId());
725+
}
726+
727+
@And("Update discount with {string} amount on Working Capital loan account failed due to already added discount before disbursement")
728+
public void updateDiscountWCLoanAlreadyAddedFailure(String discountAmount) {
729+
String errorMessage = ErrorMessageHelper.discountAlreadySetBeforeDisburseFailure();
730+
updateDiscountFailedCheck(discountAmount, errorMessage);
731+
}
732+
733+
@And("Update discount with {string} amount on Working Capital loan account failed due to date diff from disbursement date")
734+
public void updateDiscountWCLoanDiffFromDisburseDateFailure(String discountAmount) {
735+
String errorMessage = ErrorMessageHelper.discountDiffDateFromDisburseFailure();
736+
updateDiscountFailedCheck(discountAmount, errorMessage);
737+
}
738+
739+
@And("Update discount with {string} amount on Working Capital loan account failed due to override disallowed by product")
740+
public void updateDiscountWCLoanOverrideDisallowedByProductFailure(String discountAmount) {
741+
String errorMessage = ErrorMessageHelper.discountOverrideDisallowedByProductFailure();
742+
updateDiscountFailedCheck(discountAmount, errorMessage);
743+
}
744+
630745
// ====================================
631746
// Private Helper Methods
632747
// ====================================
@@ -637,6 +752,7 @@ private void createWorkingCapitalLoanAccount(final List<String> loanData) {
637752
final Long clientId = extractClientId();
638753
final Long loanProductId = resolveLoanProductId(loanProduct);
639754
final PostWorkingCapitalLoansRequest loansRequest = buildCreateLoanRequest(clientId, loanProductId, loanData);
755+
testContext().set(TestContextKey.LOAN_CREATE_REQUEST, loansRequest);
640756

641757
final PostWorkingCapitalLoansResponse response = ok(
642758
() -> fineractClient.workingCapitalLoans().submitWorkingCapitalLoanApplication(loansRequest));
@@ -698,6 +814,19 @@ private void executeStateTransition(final String command, final PostWorkingCapit
698814
testContext().set(responseKey, response);
699815
}
700816

817+
public void updateDiscountFailedCheck(String discountAmount, String errorMessage) {
818+
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
819+
long loanId = loanResponse.getLoanId();
820+
821+
PutWorkingCapitalLoansLoanIdDiscountRequest updateDiscountRequest = workingCapitalLoanRequestFactory
822+
.defaultWorkingCapitalLoanUpdateDiscountRequest().discountAmount(new BigDecimal(discountAmount));
823+
824+
CallFailedRuntimeException exception = fail(
825+
() -> fineractClient.workingCapitalLoans().updateWorkingCapitalLoanDiscountById(loanId, updateDiscountRequest));
826+
assertThat(exception.getStatus()).as(errorMessage).isEqualTo(400);
827+
assertThat(exception.getDeveloperMessage()).contains(errorMessage);
828+
}
829+
701830
// Data Extraction Helpers
702831
private Long getCreatedLoanId() {
703832
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);

0 commit comments

Comments
 (0)