Skip to content

Commit c0e8525

Browse files
authored
Merge branch 'apache:develop' into FINERACT-1152-fix-loan-reschedule-end-date-validation
2 parents dc31cd1 + 7597eed commit c0e8525

44 files changed

Lines changed: 3495 additions & 41 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,22 @@ public CommandWrapperBuilder undoWorkingCapitalLoanApplicationApproval(final Lon
589589
return this;
590590
}
591591

592+
public CommandWrapperBuilder disburseWorkingCapitalLoanApplication(final Long loanId) {
593+
this.actionName = "DISBURSE";
594+
this.entityName = "WORKINGCAPITALLOAN";
595+
this.entityId = loanId;
596+
this.href = "/workingcapitalloans/" + loanId;
597+
return this;
598+
}
599+
600+
public CommandWrapperBuilder undoWorkingCapitalLoanApplicationDisbursal(final Long loanId) {
601+
this.actionName = "DISBURSALUNDO";
602+
this.entityName = "WORKINGCAPITALLOAN";
603+
this.entityId = loanId;
604+
this.href = "/workingcapitalloans/" + loanId;
605+
return this;
606+
}
607+
592608
public CommandWrapperBuilder createClientIdentifier(final Long clientId) {
593609
this.actionName = "CREATE";
594610
this.entityName = "CLIENTIDENTIFIER";

fineract-core/src/main/java/org/apache/fineract/portfolio/paymentdetail/data/PaymentDetailData.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.apache.fineract.portfolio.paymentdetail.data;
2020

2121
import java.io.Serializable;
22+
import lombok.Builder;
2223
import lombok.EqualsAndHashCode;
2324
import lombok.Getter;
2425
import lombok.RequiredArgsConstructor;
@@ -30,6 +31,7 @@
3031
@Getter
3132
@EqualsAndHashCode
3233
@RequiredArgsConstructor
34+
@Builder
3335
public class PaymentDetailData implements Serializable {
3436

3537
private final Long id;

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,19 @@ public PostWorkingCapitalLoansLoanIdRequest defaultWorkingCapitalLoanUndoApprova
8787
.dateFormat(DATE_FORMAT)//
8888
.locale(DEFAULT_LOCALE);//
8989
}
90+
91+
public PostWorkingCapitalLoansLoanIdRequest defaultWorkingCapitalLoanDisburseRequest() {
92+
return new PostWorkingCapitalLoansLoanIdRequest()//
93+
.actualDisbursementDate(DATE_SUBMIT_STRING)//
94+
.transactionAmount(DEFAULT_PRINCIPAL)//
95+
.dateFormat(DATE_FORMAT)//
96+
.locale(DEFAULT_LOCALE);//
97+
}
98+
99+
public PostWorkingCapitalLoansLoanIdRequest defaultWorkingCapitalLoanUndoDisburseRequest() {
100+
return new PostWorkingCapitalLoansLoanIdRequest()//
101+
.note("")//
102+
.dateFormat(DATE_FORMAT)//
103+
.locale(DEFAULT_LOCALE);//
104+
}
90105
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,4 +1056,16 @@ public static String workingCapitalDelinquencyBucketNotFoundFailure(Long id) {
10561056
public static String workingCapitalDelinquencyBucketDoesntExistFailure(Long id) {
10571057
return String.format("Delinquency bucket with id `%d` does not exist.", id);
10581058
}
1059+
1060+
public static String disburseNotApprovedFailure(String status) {
1061+
return String.format("Disbursement is not allowed from current status %s", status);
1062+
}
1063+
1064+
public static String disburseDateFailure(String errorMessageDescription) {
1065+
return String.format("Failed data validation due to: %s", errorMessageDescription);
1066+
}
1067+
1068+
public static String undoDisbursalDisallowedFailure(String status) {
1069+
return String.format("Transition LOAN_DISBURSAL_UNDO is not allowed from status %s", status);
1070+
}
10591071
}

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

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@
2020

2121
import static org.apache.fineract.client.feign.util.FeignCalls.fail;
2222
import static org.apache.fineract.client.feign.util.FeignCalls.ok;
23+
import static org.apache.fineract.test.data.LoanStatus.ACTIVE;
24+
import static org.apache.fineract.test.data.LoanStatus.APPROVED;
25+
import static org.apache.fineract.test.data.LoanStatus.SUBMITTED_AND_PENDING_APPROVAL;
2326
import static org.assertj.core.api.Assertions.assertThat;
2427
import static org.junit.jupiter.api.Assertions.assertNotNull;
2528
import static org.junit.jupiter.api.Assertions.assertTrue;
2629

2730
import io.cucumber.datatable.DataTable;
31+
import io.cucumber.java.en.And;
2832
import io.cucumber.java.en.Then;
2933
import io.cucumber.java.en.When;
3034
import java.math.BigDecimal;
@@ -37,6 +41,7 @@
3741
import org.apache.fineract.client.feign.FineractFeignClient;
3842
import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
3943
import org.apache.fineract.client.models.DeleteWorkingCapitalLoansLoanIdResponse;
44+
import org.apache.fineract.client.models.GetDisbursementDetail;
4045
import org.apache.fineract.client.models.GetWorkingCapitalLoansLoanIdResponse;
4146
import org.apache.fineract.client.models.PostClientsResponse;
4247
import org.apache.fineract.client.models.PostWorkingCapitalLoansLoanIdRequest;
@@ -45,9 +50,11 @@
4550
import org.apache.fineract.client.models.PostWorkingCapitalLoansResponse;
4651
import org.apache.fineract.client.models.PutWorkingCapitalLoansLoanIdRequest;
4752
import org.apache.fineract.client.models.PutWorkingCapitalLoansLoanIdResponse;
53+
import org.apache.fineract.test.data.LoanStatus;
4854
import org.apache.fineract.test.data.workingcapitalproduct.DefaultWorkingCapitalLoanProduct;
4955
import org.apache.fineract.test.data.workingcapitalproduct.WorkingCapitalLoanProductResolver;
5056
import org.apache.fineract.test.factory.WorkingCapitalLoanRequestFactory;
57+
import org.apache.fineract.test.helper.ErrorMessageHelper;
5158
import org.apache.fineract.test.helper.Utils;
5259
import org.apache.fineract.test.messaging.event.EventCheckHelper;
5360
import org.apache.fineract.test.stepdef.AbstractStepDef;
@@ -378,6 +385,23 @@ public void adminAttemptsToModifyNonExistentWorkingCapitalLoan() {
378385
log.info("Attempted to modify non-existent working capital loan ID {}", NON_EXISTENT_LOAN_ID);
379386
}
380387

388+
@Then("Modifying the working capital loan that is Disbursed in Active state results in an error")
389+
public void modifyingDisbursedWithActiveStateLoanResultsInAnError() {
390+
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
391+
long loanId = loanResponse.getLoanId();
392+
393+
final PutWorkingCapitalLoansLoanIdRequest modifyRequest = workingCapitalLoanRequestFactory
394+
.defaultModifyWorkingCapitalLoansRequest();
395+
396+
final CallFailedRuntimeException exception = fail(
397+
() -> fineractClient.workingCapitalLoans().modifyWorkingCapitalLoanApplicationById(getCreatedLoanId(), modifyRequest, ""));
398+
399+
assertThat(exception.getStatus()).as("HTTP status code should be 403").isEqualTo(403);
400+
assertThat(exception.getMessage()).as("Should contain no parameters error")
401+
.contains(String.format("Working Capital Loan with identifier %d cannot be modified in its current state.", loanId));
402+
log.info("Verified modification failed with disbursed Active status empty request");
403+
}
404+
381405
@Then("Working capital loan modification fails with a 404 not found error")
382406
public void workingCapitalLoanModificationFailsWith404() {
383407
final CallFailedRuntimeException exception = testContext().get(TestContextKey.LOAN_MODIFY_RESPONSE);
@@ -465,6 +489,137 @@ public void verifyWorkingCapitalLoanUndoApprovalSuccess() {
465489
verifyStateTransitionSuccess(TestContextKey.LOAN_UNDO_APPROVAL_RESPONSE, "undo approval");
466490
}
467491

492+
@When("Undo approval on the working capital loan results an error with the following data:")
493+
public void undoApprovalWorkingCapitalLoan(final DataTable table) {
494+
final PostWorkingCapitalLoansLoanIdRequest undoApprovalRequest = workingCapitalLoanRequestFactory
495+
.defaultWorkingCapitalLoanUndoApprovalRequest();
496+
497+
final CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoans()
498+
.stateTransitionWorkingCapitalLoanById(getCreatedLoanId(), "undoApproval", undoApprovalRequest));
499+
500+
verifyErrorResponse(exception, table);
501+
log.info("Verified working capital loan undo approval failed with expected error");
502+
}
503+
504+
@Then("Working Capital loan status will be {string}")
505+
public void loanWCStatus(String statusExpected) {
506+
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
507+
long loanId = loanResponse.getLoanId();
508+
String resourceId = String.valueOf(loanId);
509+
510+
GetWorkingCapitalLoansLoanIdResponse loanDetailsResponse = ok(
511+
() -> fineractClient.workingCapitalLoans().retrieveWorkingCapitalLoanById(loanId));
512+
513+
testContext().set(TestContextKey.LOAN_RESPONSE, loanDetailsResponse);
514+
Long loanStatusActualValue = loanDetailsResponse.getStatus().getId();
515+
516+
LoanStatus loanStatusExpected = LoanStatus.valueOf(statusExpected);
517+
Long loanStatusExpectedValue = loanStatusExpected.getValue().longValue();
518+
519+
assertThat(loanStatusActualValue)
520+
.as(ErrorMessageHelper.wrongLoanStatus(resourceId, loanStatusActualValue.intValue(), loanStatusExpectedValue.intValue()))
521+
.isEqualTo(loanStatusExpectedValue);
522+
}
523+
524+
@And("Admin successfully disburse the Working Capital loan on {string} with {string} EUR transaction amount")
525+
public void disburseWCLoan(String actualDisbursementDate, String transactionAmount) {
526+
PostWorkingCapitalLoansLoanIdRequest disburseRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoanDisburseRequest()
527+
.actualDisbursementDate(actualDisbursementDate)//
528+
.transactionAmount(new BigDecimal(transactionAmount));
529+
530+
executeStateTransition("disburse", disburseRequest, TestContextKey.LOAN_DISBURSE_RESPONSE, false);
531+
verifyStateTransitionSuccess(TestContextKey.LOAN_DISBURSE_RESPONSE, "disbursement");
532+
checkChangesExpectedStatus(TestContextKey.LOAN_DISBURSE_RESPONSE, ACTIVE);
533+
}
534+
535+
@And("Admin successfully disburse the Working Capital loan by externalId on {string} with {string} EUR transaction amount")
536+
public void disburseWCLoanByExternalId(String actualDisbursementDate, String transactionAmount) {
537+
PostWorkingCapitalLoansLoanIdRequest disburseRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoanDisburseRequest()
538+
.actualDisbursementDate(actualDisbursementDate)//
539+
.transactionAmount(new BigDecimal(transactionAmount));
540+
541+
executeStateTransition("disburse", disburseRequest, TestContextKey.LOAN_DISBURSE_RESPONSE, true);
542+
verifyStateTransitionSuccess(TestContextKey.LOAN_DISBURSE_RESPONSE, "disbursement");
543+
checkChangesExpectedStatus(TestContextKey.LOAN_DISBURSE_RESPONSE, ACTIVE);
544+
}
545+
546+
@Then("Verify Working Capital loan disbursement was successful on {string} with {string} EUR transaction amount")
547+
public void checkDisbursementData(String actualDisbursementDate, String transactionAmount) {
548+
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
549+
long loanId = loanResponse.getLoanId();
550+
551+
GetWorkingCapitalLoansLoanIdResponse loanDetailsResponse = ok(
552+
() -> fineractClient.workingCapitalLoans().retrieveWorkingCapitalLoanById(loanId));
553+
String getLoanStatus = loanDetailsResponse.getStatus().getValue();
554+
assertThat(getLoanStatus.toUpperCase()).isEqualTo(ACTIVE.name());
555+
556+
GetDisbursementDetail disbursementDetails = loanDetailsResponse.getDisbursementDetails().stream().findFirst()
557+
.orElseThrow(() -> new RuntimeException(""));
558+
String formattedDate = disbursementDetails.getActualDisbursementDate().format(FORMATTER);
559+
assertThat(formattedDate).isEqualTo(actualDisbursementDate);
560+
assertThat(disbursementDetails.getActualAmount().compareTo(new BigDecimal(transactionAmount))).isEqualTo(0);
561+
}
562+
563+
@Then("Admin successfully undo Working Capital disbursal")
564+
public void undoDisbursalWCLoan() {
565+
PostWorkingCapitalLoansLoanIdRequest undoDisbursalRequest = workingCapitalLoanRequestFactory
566+
.defaultWorkingCapitalLoanUndoDisburseRequest();
567+
568+
executeStateTransition("undodisbursal", undoDisbursalRequest, TestContextKey.LOAN_UNDO_DISBURSE_RESPONSE, false);
569+
verifyStateTransitionSuccess(TestContextKey.LOAN_UNDO_DISBURSE_RESPONSE, "undoDisbursement");
570+
checkChangesExpectedStatus(TestContextKey.LOAN_UNDO_DISBURSE_RESPONSE, APPROVED);
571+
}
572+
573+
@Then("Admin successfully undo Working Capital disbursal by externalId")
574+
public void undoDisbursalWCLoanByexternalId() {
575+
PostWorkingCapitalLoansLoanIdRequest undoDisbursalRequest = workingCapitalLoanRequestFactory
576+
.defaultWorkingCapitalLoanUndoDisburseRequest();
577+
578+
executeStateTransition("undodisbursal", undoDisbursalRequest, TestContextKey.LOAN_UNDO_DISBURSE_RESPONSE, true);
579+
verifyStateTransitionSuccess(TestContextKey.LOAN_UNDO_DISBURSE_RESPONSE, "undoDisbursement");
580+
checkChangesExpectedStatus(TestContextKey.LOAN_UNDO_DISBURSE_RESPONSE, APPROVED);
581+
}
582+
583+
@Then("Admin fails to disburse the Working Capital loan on {string} with {string} EUR transaction amount because of not approved")
584+
public void disburseWCLoanFailureWithNotApproved(String actualDisbursementDate, String transactionAmount) {
585+
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
586+
long loanId = loanResponse.getLoanId();
587+
PostWorkingCapitalLoansLoanIdRequest disburseRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoanDisburseRequest()
588+
.actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount));
589+
590+
CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoans().stateTransitionWorkingCapitalLoanById(loanId,
591+
disburseRequest, Map.of("command", "disburse")));
592+
assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(400);
593+
assertThat(exception.getDeveloperMessage())
594+
.contains(ErrorMessageHelper.disburseNotApprovedFailure(SUBMITTED_AND_PENDING_APPROVAL.name()));
595+
}
596+
597+
@Then("Admin fails to disburse the Working Capital loan on {string} with {string} EUR transaction amount with invalid data outcomes with error message {string}")
598+
public void disburseWCLoanFailureWithInvalidData(String actualDisbursementDate, String transactionAmount,
599+
String errorMessageDescription) {
600+
String errorMessage = ErrorMessageHelper.disburseDateFailure(errorMessageDescription);
601+
disburseWCLoanFailure(actualDisbursementDate, transactionAmount, 400, errorMessage);
602+
}
603+
604+
@Then("Admin fails to disburse the Working Capital loan on {string} with {string} EUR transaction amount without mandatory data outcomes with error message {string}")
605+
public void disburseWCLoanFailureWithoutMandatoryData(String actualDisbursementDate, String transactionAmount, String errorMessage) {
606+
disburseWCLoanFailure(actualDisbursementDate, transactionAmount, 400, errorMessage);
607+
}
608+
609+
@Then("Admin fails to undo disbursal the Working Capital loan due to loan status {string}")
610+
public void undoDisbursalWCLoanFailure(String actualLoanStatus) {
611+
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
612+
long loanId = loanResponse.getLoanId();
613+
614+
PostWorkingCapitalLoansLoanIdRequest undoDisbursalRequest = workingCapitalLoanRequestFactory
615+
.defaultWorkingCapitalLoanUndoDisburseRequest();
616+
617+
CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoans().stateTransitionWorkingCapitalLoanById(loanId,
618+
undoDisbursalRequest, Map.of("command", "undodisbursal")));
619+
assertThat(exception.getStatus()).as(ErrorMessageHelper.undoDisbursalDisallowedFailure(actualLoanStatus)).isEqualTo(400);
620+
assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.undoDisbursalDisallowedFailure(actualLoanStatus));
621+
}
622+
468623
// ====================================
469624
// Private Helper Methods
470625
// ====================================
@@ -668,4 +823,27 @@ private void verifyErrorResponse(final CallFailedRuntimeException exception, fin
668823
.isEqualTo(Integer.parseInt(expectedHttpCode));
669824
assertThat(exception.getMessage()).as("Should contain error message").contains(expectedErrorMessage);
670825
}
826+
827+
public void checkChangesExpectedStatus(String responseKey, LoanStatus expectedStatus) {
828+
final PostWorkingCapitalLoansLoanIdResponse response = testContext().get(responseKey);
829+
final Object changes = response.getChanges();
830+
assertThat(changes).as("Changes map").isNotNull().isInstanceOf(Map.class);
831+
832+
@SuppressWarnings("unchecked")
833+
final Map<String, Object> changesMap = (Map<String, Object>) changes;
834+
assertThat(changesMap).as("Changes map should contain value '%s'", expectedStatus).containsValue(expectedStatus.name());
835+
}
836+
837+
public void disburseWCLoanFailure(String actualDisbursementDate, String transactionAmount, int errorCode, String errorMessage) {
838+
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
839+
long loanId = loanResponse.getLoanId();
840+
PostWorkingCapitalLoansLoanIdRequest disburseRequest = workingCapitalLoanRequestFactory.defaultWorkingCapitalLoanDisburseRequest()
841+
.actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount));
842+
843+
CallFailedRuntimeException exception = fail(() -> fineractClient.workingCapitalLoans().stateTransitionWorkingCapitalLoanById(loanId,
844+
disburseRequest, Map.of("command", "disburse")));
845+
assertThat(exception.getStatus()).as(errorMessage).isEqualTo(errorCode);
846+
assertThat(exception.getDeveloperMessage()).contains(errorMessage);
847+
}
848+
671849
}

0 commit comments

Comments
 (0)