Skip to content

Commit 7adf123

Browse files
FINERACT-2455: WC delinquency RESCHEDULE action to change minimum payment amount and frequency
1 parent e5c0dff commit 7adf123

17 files changed

Lines changed: 664 additions & 46 deletions

File tree

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.test.stepdef.loan;
20+
21+
import static org.apache.fineract.client.feign.util.FeignCalls.fail;
22+
import static org.apache.fineract.client.feign.util.FeignCalls.ok;
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
25+
import io.cucumber.datatable.DataTable;
26+
import io.cucumber.java.en.Then;
27+
import io.cucumber.java.en.When;
28+
import java.math.BigDecimal;
29+
import java.time.LocalDate;
30+
import java.time.format.DateTimeFormatter;
31+
import java.util.List;
32+
import java.util.Map;
33+
import lombok.RequiredArgsConstructor;
34+
import lombok.extern.slf4j.Slf4j;
35+
import org.apache.fineract.client.feign.FineractFeignClient;
36+
import org.apache.fineract.client.models.DelinquencyBucketRequest;
37+
import org.apache.fineract.client.models.MinimumPaymentPeriodAndRule;
38+
import org.apache.fineract.client.models.PostAllowAttributeOverrides;
39+
import org.apache.fineract.client.models.PostDelinquencyBucketResponse;
40+
import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsRequest;
41+
import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsResponse;
42+
import org.apache.fineract.client.models.PostWorkingCapitalLoansDelinquencyActionRequest;
43+
import org.apache.fineract.client.models.PostWorkingCapitalLoansDelinquencyActionResponse;
44+
import org.apache.fineract.client.models.PostWorkingCapitalLoansResponse;
45+
import org.apache.fineract.client.models.WorkingCapitalLoanDelinquencyActionData;
46+
import org.apache.fineract.client.models.WorkingCapitalLoanDelinquencyRangeScheduleData;
47+
import org.apache.fineract.test.factory.WorkingCapitalRequestFactory;
48+
import org.apache.fineract.test.helper.Utils;
49+
import org.apache.fineract.test.stepdef.AbstractStepDef;
50+
import org.apache.fineract.test.support.TestContext;
51+
import org.apache.fineract.test.support.TestContextKey;
52+
53+
@Slf4j
54+
@RequiredArgsConstructor
55+
public class WorkingCapitalDelinquencyRescheduleStepDef extends AbstractStepDef {
56+
57+
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd MMMM yyyy");
58+
59+
private final FineractFeignClient fineractFeignClient;
60+
61+
private final WorkingCapitalRequestFactory workingCapitalRequestFactory;
62+
63+
@When("Admin creates a new Working Capital Loan Product with delinquency bucket")
64+
public void createProductWithDelinquencyBucket() {
65+
final Long bucketId = TestContext.GLOBAL.get(TestContextKey.DELINQUENCY_BUCKET_ID);
66+
assertThat(bucketId).isNotNull();
67+
68+
final PostWorkingCapitalLoanProductsRequest request = workingCapitalRequestFactory.defaultWorkingCapitalLoanProductRequest()
69+
.name("WCLP-DLQ-" + Utils.randomStringGenerator(8)).delinquencyBucketId(bucketId)
70+
.allowAttributeOverrides(new PostAllowAttributeOverrides().discountDefault(true));
71+
final PostWorkingCapitalLoanProductsResponse response = ok(
72+
() -> fineractFeignClient.workingCapitalLoanProducts().createWorkingCapitalLoanProduct(request));
73+
testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE, response);
74+
testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST, request);
75+
log.info("Created WC product id={} with delinquency bucket id={}", response.getResourceId(), bucketId);
76+
}
77+
78+
@When("Admin creates WC Delinquency Bucket with frequency {int} {word} and minimumPayment {int} {word}")
79+
public void createWcDelinquencyBucket(final int frequency, final String frequencyType, final int minimumPayment,
80+
final String minimumPaymentType) {
81+
final DelinquencyBucketRequest request = new DelinquencyBucketRequest().name("DB-WCL-" + Utils.randomStringGenerator(12))
82+
.bucketType("WORKING_CAPITAL").ranges(List.of(1L))
83+
.minimumPaymentPeriodAndRule(new MinimumPaymentPeriodAndRule().frequency(frequency).frequencyType(frequencyType)
84+
.minimumPayment(new BigDecimal(minimumPayment)).minimumPaymentType(minimumPaymentType));
85+
86+
final PostDelinquencyBucketResponse result = ok(
87+
() -> fineractFeignClient.delinquencyRangeAndBucketsManagement().createBucket(request));
88+
assertThat(result).isNotNull();
89+
assertThat(result.getResourceId()).isNotNull();
90+
TestContext.GLOBAL.set(TestContextKey.DELINQUENCY_BUCKET_ID, result.getResourceId());
91+
log.info("Created WC delinquency bucket id={} with frequency={} {} minimumPayment={} {}", result.getResourceId(), frequency,
92+
frequencyType, minimumPayment, minimumPaymentType);
93+
}
94+
95+
@When("Admin creates WC delinquency reschedule action with minimumPayment {int} and frequency {int} {word}")
96+
public void createRescheduleAction(final int minimumPayment, final int frequency, final String frequencyType) {
97+
final Long loanId = getLoanId();
98+
final PostWorkingCapitalLoansDelinquencyActionRequest request = buildRescheduleRequest(new BigDecimal(minimumPayment), frequency,
99+
frequencyType);
100+
log.info("Creating RESCHEDULE action for WC loan {}: minimumPayment={}, frequency={} {}", loanId, minimumPayment, frequency,
101+
frequencyType);
102+
103+
final PostWorkingCapitalLoansDelinquencyActionResponse result = ok(
104+
() -> fineractFeignClient.workingCapitalLoanDelinquencyActions().createDelinquencyAction(loanId, request));
105+
assertThat(result).isNotNull();
106+
assertThat(result.getResourceId()).isNotNull();
107+
log.info("RESCHEDULE action created with id={}", result.getResourceId());
108+
}
109+
110+
@Then("Admin fails to create WC delinquency reschedule action with minimumPayment {int} and frequency {int} {word}")
111+
public void failToCreateRescheduleAction(final int minimumPayment, final int frequency, final String frequencyType) {
112+
final Long loanId = getLoanId();
113+
final PostWorkingCapitalLoansDelinquencyActionRequest request = buildRescheduleRequest(new BigDecimal(minimumPayment), frequency,
114+
frequencyType);
115+
log.info("Attempting to create RESCHEDULE action for WC loan {} (expecting failure): minimumPayment={}, frequency={} {}", loanId,
116+
minimumPayment, frequency, frequencyType);
117+
118+
fail(() -> fineractFeignClient.workingCapitalLoanDelinquencyActions().createDelinquencyAction(loanId, request));
119+
}
120+
121+
@Then("WC loan delinquency range schedule has the following periods:")
122+
public void verifyPeriods(final DataTable table) {
123+
final Long loanId = getLoanId();
124+
final List<WorkingCapitalLoanDelinquencyRangeScheduleData> periods = ok(
125+
() -> fineractFeignClient.workingCapitalLoanDelinquencyRangeSchedule().retrieveDelinquencyRangeSchedule(loanId));
126+
127+
final List<Map<String, String>> expectedRows = table.asMaps();
128+
assertThat(periods).as("Number of periods").hasSize(expectedRows.size());
129+
130+
for (int i = 0; i < expectedRows.size(); i++) {
131+
final Map<String, String> expected = expectedRows.get(i);
132+
final WorkingCapitalLoanDelinquencyRangeScheduleData actual = periods.get(i);
133+
final String periodLabel = "Period " + (i + 1);
134+
135+
assertThat(actual.getPeriodNumber()).as(periodLabel + " periodNumber")
136+
.isEqualTo(Integer.parseInt(expected.get("periodNumber")));
137+
assertThat(actual.getFromDate()).as(periodLabel + " fromDate")
138+
.isEqualTo(LocalDate.parse(expected.get("fromDate"), DATE_FORMAT));
139+
assertThat(actual.getToDate()).as(periodLabel + " toDate").isEqualTo(LocalDate.parse(expected.get("toDate"), DATE_FORMAT));
140+
assertThat(actual.getExpectedAmount()).as(periodLabel + " expectedAmount")
141+
.isEqualByComparingTo(new BigDecimal(expected.get("expectedAmount")));
142+
assertThat(actual.getPaidAmount()).as(periodLabel + " paidAmount")
143+
.isEqualByComparingTo(new BigDecimal(expected.get("paidAmount")));
144+
assertThat(actual.getOutstandingAmount()).as(periodLabel + " outstandingAmount")
145+
.isEqualByComparingTo(new BigDecimal(expected.get("outstandingAmount")));
146+
147+
final String criteriaMetStr = expected.get("minPaymentCriteriaMet");
148+
if (criteriaMetStr == null || criteriaMetStr.isBlank()) {
149+
assertThat(actual.getMinPaymentCriteriaMet()).as(periodLabel + " minPaymentCriteriaMet").isNull();
150+
} else {
151+
assertThat(actual.getMinPaymentCriteriaMet()).as(periodLabel + " minPaymentCriteriaMet")
152+
.isEqualTo(Boolean.parseBoolean(criteriaMetStr));
153+
}
154+
}
155+
}
156+
157+
@Then("WC loan delinquency actions contain {int} action(s)")
158+
public void verifyActionCount(final int count) {
159+
final Long loanId = getLoanId();
160+
final List<WorkingCapitalLoanDelinquencyActionData> actions = ok(
161+
() -> fineractFeignClient.workingCapitalLoanDelinquencyActions().retrieveDelinquencyActions(loanId));
162+
163+
assertThat(actions).hasSize(count);
164+
}
165+
166+
private Long getLoanId() {
167+
final PostWorkingCapitalLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
168+
assertThat(loanResponse).isNotNull();
169+
return loanResponse.getLoanId();
170+
}
171+
172+
private PostWorkingCapitalLoansDelinquencyActionRequest buildRescheduleRequest(final BigDecimal minimumPayment, final int frequency,
173+
final String frequencyType) {
174+
final PostWorkingCapitalLoansDelinquencyActionRequest request = new PostWorkingCapitalLoansDelinquencyActionRequest();
175+
request.setAction("reschedule");
176+
request.setMinimumPayment(minimumPayment);
177+
request.setFrequency(frequency);
178+
request.setFrequencyType(frequencyType);
179+
request.setLocale("en");
180+
return request;
181+
}
182+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,9 +757,18 @@ private void createWorkingCapitalLoanAccount(final List<String> loanData) {
757757
final PostWorkingCapitalLoansResponse response = ok(
758758
() -> fineractClient.workingCapitalLoans().submitWorkingCapitalLoanApplication(loansRequest));
759759
testContext().set(TestContextKey.LOAN_CREATE_RESPONSE, response);
760+
trackLoanIdIfEnabled(response.getLoanId());
760761
log.info("Working Capital Loan created with ID: {}", response.getLoanId());
761762
}
762763

764+
@SuppressWarnings("unchecked")
765+
private void trackLoanIdIfEnabled(final Long loanId) {
766+
final List<Long> trackedIds = testContext().get(TestContextKey.WC_LOAN_IDS);
767+
if (trackedIds != null) {
768+
trackedIds.add(loanId);
769+
}
770+
}
771+
763772
private void modifyWorkingCapitalLoanAccount(final List<String> loanData) {
764773
final PutWorkingCapitalLoansLoanIdRequest modifyRequest = buildModifyLoanRequest(loanData);
765774

@@ -839,6 +848,11 @@ private Long extractClientId() {
839848
}
840849

841850
private Long resolveLoanProductId(final String loanProductName) {
851+
if ("WCLP_DELINQUENCY".equals(loanProductName)) {
852+
final PostWorkingCapitalLoanProductsResponse response = testContext()
853+
.get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE);
854+
return response.getResourceId();
855+
}
842856
final DefaultWorkingCapitalLoanProduct product = DefaultWorkingCapitalLoanProduct.valueOf(loanProductName);
843857
return workingCapitalLoanProductResolver.resolve(product);
844858
}

0 commit comments

Comments
 (0)