Skip to content

Commit 83fcc5c

Browse files
authored
Merge pull request #5720
FINERACT-2455: WC - Breach Management - Configuration
2 parents 43a8303 + 3933bb0 commit 83fcc5c

File tree

66 files changed

+2482
-64
lines changed

Some content is hidden

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

66 files changed

+2482
-64
lines changed

fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
import org.apache.fineract.client.feign.services.TwoFactorApi;
155155
import org.apache.fineract.client.feign.services.UserGeneratedDocumentsApi;
156156
import org.apache.fineract.client.feign.services.UsersApi;
157+
import org.apache.fineract.client.feign.services.WorkingCapitalBreachApi;
157158
import org.apache.fineract.client.feign.services.WorkingCapitalLoanCobCatchUpApi;
158159
import org.apache.fineract.client.feign.services.WorkingCapitalLoanDelinquencyActionsApi;
159160
import org.apache.fineract.client.feign.services.WorkingCapitalLoanDelinquencyRangeScheduleApi;
@@ -777,6 +778,10 @@ public WorkingCapitalLoanTransactionsApi workingCapitalLoanTransactions() {
777778
return create(WorkingCapitalLoanTransactionsApi.class);
778779
}
779780

781+
public WorkingCapitalBreachApi workingCapitalBreaches() {
782+
return create(WorkingCapitalBreachApi.class);
783+
}
784+
780785
public WorkingDaysApi workingDays() {
781786
return create(WorkingDaysApi.class);
782787
}

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,30 @@ public CommandWrapperBuilder deleteWorkingCapitalLoanProduct(final Long productI
540540
return this;
541541
}
542542

543+
public CommandWrapperBuilder createWorkingCapitalBreach() {
544+
this.actionName = "CREATE";
545+
this.entityName = "WORKINGCAPITALBREACH";
546+
this.entityId = null;
547+
this.href = "/working-capital-breach/breaches";
548+
return this;
549+
}
550+
551+
public CommandWrapperBuilder updateWorkingCapitalBreach(final Long breachId) {
552+
this.actionName = "UPDATE";
553+
this.entityName = "WORKINGCAPITALBREACH";
554+
this.entityId = breachId;
555+
this.href = "/working-capital-breach/breaches/" + breachId;
556+
return this;
557+
}
558+
559+
public CommandWrapperBuilder deleteWorkingCapitalBreach(final Long breachId) {
560+
this.actionName = "DELETE";
561+
this.entityName = "WORKINGCAPITALBREACH";
562+
this.entityId = breachId;
563+
this.href = "/working-capital-breach/breaches/" + breachId;
564+
return this;
565+
}
566+
543567
public CommandWrapperBuilder createWorkingCapitalLoanApplication() {
544568
this.actionName = "CREATE";
545569
this.entityName = "WORKINGCAPITALLOAN";

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
public enum DefaultWorkingCapitalLoanProduct implements WorkingCapitalLoanProduct {
2222

2323
WCLP, //
24+
WCLP_DISALLOW_ATTRIBUTES_OVERRIDE, //
2425
WCLP_FOR_UPDATE; //
2526

2627
@Override

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.apache.fineract.client.models.PostPaymentAllocation;
4242
import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsRequest;
4343
import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdRequest;
44+
import org.apache.fineract.client.models.WorkingCapitalBreachRequest;
4445
import org.apache.fineract.test.data.delinquency.DelinquencyBucketType;
4546
import org.apache.fineract.test.data.delinquency.DelinquencyFrequencyType;
4647
import org.apache.fineract.test.data.delinquency.DelinquencyMinimumPayment;
@@ -60,6 +61,10 @@ public class WorkingCapitalRequestFactory {
6061
public static final String PENALTY = "PENALTY";
6162
public static final String FEE = "FEE";
6263
public static final String PRINCIPAL = "PRINCIPAL";
64+
public static final Integer DEFAULT_WC_BREACH_FREQUENCY = 2;
65+
public static final String DEFAULT_WC_BREACH_FREQUENCY_TYPE = "MONTHS";
66+
public static final String DEFAULT_WC_BREACH_AMOUNT_CALCULATION_TYPE = "PERCENTAGE";
67+
public static final BigDecimal DEFAULT_WC_BREACH_AMOUNT = new BigDecimal("1.23");
6368

6469
public PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductRequest() {
6570
String name = Utils.randomStringGenerator(WCLP_NAME_PREFIX, 10);
@@ -91,6 +96,18 @@ public PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductReq
9196
List.of(PENALTY, FEE, PRINCIPAL))));//
9297
}
9398

99+
public PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductAllowAttributesOverrideRequest() {
100+
String name = Utils.randomStringGenerator(WCLP_NAME_PREFIX, 10);
101+
String shortName = loanProductsRequestFactory.generateShortNameSafely();
102+
103+
PostAllowAttributeOverrides allowAttributeOverrides = new PostAllowAttributeOverrides().delinquencyBucketClassification(true)
104+
.breach(true).discountDefault(true).periodPaymentFrequencyType(true).periodPaymentFrequency(true);
105+
106+
return defaultWorkingCapitalLoanProductRequest().name(name)//
107+
.shortName(shortName)//
108+
.allowAttributeOverrides(allowAttributeOverrides);
109+
}
110+
94111
public PutWorkingCapitalLoanProductsProductIdRequest defaultWorkingCapitalLoanProductRequestUpdate() {
95112
String name = Utils.randomStringGenerator(WCLP_NAME_PREFIX, 10);
96113
String shortName = loanProductsRequestFactory.generateShortNameSafely();
@@ -177,6 +194,14 @@ public DelinquencyBucketRequest defaultWorkingCapitalDelinquencyBucketRequest()
177194
.minimumPayment(new BigDecimal("1.23")));
178195
}
179196

197+
public WorkingCapitalBreachRequest defaultWorkingCapitalBreachRequest() {
198+
return new WorkingCapitalBreachRequest() //
199+
.breachFrequency(DEFAULT_WC_BREACH_FREQUENCY) //
200+
.breachFrequencyType(DEFAULT_WC_BREACH_FREQUENCY_TYPE) //
201+
.breachAmountCalculationType(DEFAULT_WC_BREACH_AMOUNT_CALCULATION_TYPE) //
202+
.breachAmount(DEFAULT_WC_BREACH_AMOUNT);
203+
}
204+
180205
private Long getWCDelinquencyBucketIdByName(String bucketName) {
181206
try {
182207
List<DelinquencyBucketResponse> buckets = fineractClient.delinquencyRangeAndBucketsManagement().getBuckets(Map.of());

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,10 @@ public static String workingCapitalDelinquencyBucketDoesntExistFailure(Long id)
10571057
return String.format("Delinquency bucket with id `%d` does not exist.", id);
10581058
}
10591059

1060+
public static String workingCapitalBreachNotFoundFailure(final Long id) {
1061+
return String.format("Working Capital Breach with id %d was not found.", id);
1062+
}
1063+
10601064
public static String disburseNotApprovedFailure(String status) {
10611065
return String.format("Disbursement is not allowed from current status %s", status);
10621066
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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.java.en.Then;
26+
import io.cucumber.java.en.When;
27+
import java.math.BigDecimal;
28+
import lombok.RequiredArgsConstructor;
29+
import lombok.extern.slf4j.Slf4j;
30+
import org.apache.fineract.client.feign.FineractFeignClient;
31+
import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
32+
import org.apache.fineract.client.models.CommandProcessingResult;
33+
import org.apache.fineract.client.models.StringEnumOptionData;
34+
import org.apache.fineract.client.models.WorkingCapitalBreachData;
35+
import org.apache.fineract.client.models.WorkingCapitalBreachRequest;
36+
import org.apache.fineract.client.models.WorkingCapitalBreachTemplateResponse;
37+
import org.apache.fineract.test.factory.WorkingCapitalRequestFactory;
38+
import org.apache.fineract.test.helper.ErrorMessageHelper;
39+
import org.apache.fineract.test.stepdef.AbstractStepDef;
40+
import org.apache.fineract.test.support.TestContext;
41+
import org.apache.fineract.test.support.TestContextKey;
42+
import org.assertj.core.api.SoftAssertions;
43+
import org.springframework.beans.factory.annotation.Autowired;
44+
45+
@Slf4j
46+
@RequiredArgsConstructor
47+
public class WorkingCapitalBreachConfigStepDef extends AbstractStepDef {
48+
49+
@Autowired
50+
private WorkingCapitalRequestFactory workingCapitalRequestFactory;
51+
52+
private final FineractFeignClient fineractFeignClient;
53+
54+
@When("Admin Calls Breach Template")
55+
public void adminCallsBreachTemplate() {
56+
final WorkingCapitalBreachTemplateResponse template = ok(
57+
() -> fineractFeignClient.workingCapitalBreaches().retrieveWorkingCapitalBreachTemplate());
58+
assertThat(template).isNotNull();
59+
assertThat(template.getBreachFrequencyTypeOptions()).isNotNull().isNotEmpty();
60+
assertThat(template.getBreachAmountCalculationTypeOptions()).isNotNull().isNotEmpty();
61+
log.info("Template WorkingCapitalBreach: {}", template);
62+
}
63+
64+
@When("Admin creates WC Breach With Values")
65+
public void adminCreatesWCBreachWithValues() {
66+
final WorkingCapitalBreachRequest request = workingCapitalRequestFactory.defaultWorkingCapitalBreachRequest();
67+
final CommandProcessingResult response = ok(() -> fineractFeignClient.workingCapitalBreaches().createWorkingCapitalBreach(request));
68+
assertThat(response).isNotNull();
69+
assertThat(response.getResourceId()).isNotNull();
70+
TestContext.GLOBAL.set(TestContextKey.WORKING_CAPITAL_BREACH_ID, response.getResourceId());
71+
TestContext.GLOBAL.set(TestContextKey.WORKING_CAPITAL_BREACH_CREATE_REQUEST, request);
72+
}
73+
74+
@When("Admin creates WC Breach With Values for update")
75+
public void adminCreatesWCBreachWithValuesForUpdate() {
76+
final WorkingCapitalBreachRequest request = workingCapitalRequestFactory.defaultWorkingCapitalBreachRequest()
77+
.breachAmount(new BigDecimal("2.34"));
78+
final CommandProcessingResult response = ok(() -> fineractFeignClient.workingCapitalBreaches().createWorkingCapitalBreach(request));
79+
assertThat(response).isNotNull();
80+
assertThat(response.getResourceId()).isNotNull();
81+
TestContext.GLOBAL.set(TestContextKey.WORKING_CAPITAL_BREACH_ID_FOR_UPDATE, response.getResourceId());
82+
TestContext.GLOBAL.set(TestContextKey.WORKING_CAPITAL_BREACH_CREATE_REQUEST_FOR_UPDATE, request);
83+
}
84+
85+
@Then("Check created Breach has the following values")
86+
public void checkCreatedBreachHasTheFollowingValues() {
87+
final Long id = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_ID);
88+
final WorkingCapitalBreachData data = ok(() -> fineractFeignClient.workingCapitalBreaches().retrieveWorkingCapitalBreach(id));
89+
final WorkingCapitalBreachRequest request = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_CREATE_REQUEST);
90+
checkBreachData(request, data);
91+
}
92+
93+
@Then("Get Breach With Template has the following values")
94+
public void getBreachWithTemplateHasTheFollowingValues() {
95+
final Long id = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_ID);
96+
final WorkingCapitalBreachData data = ok(() -> fineractFeignClient.workingCapitalBreaches().retrieveWorkingCapitalBreach(id));
97+
final WorkingCapitalBreachTemplateResponse template = ok(
98+
() -> fineractFeignClient.workingCapitalBreaches().retrieveWorkingCapitalBreachTemplate());
99+
assertThat(data).isNotNull();
100+
assertThat(template).isNotNull();
101+
assertThat(template.getBreachFrequencyTypeOptions()).isNotNull().isNotEmpty();
102+
assertThat(template.getBreachAmountCalculationTypeOptions()).isNotNull().isNotEmpty();
103+
}
104+
105+
@When("Admin modifies WC Breach With Values")
106+
public void adminModifiesWCBreachWithValues() {
107+
final Long id = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_ID);
108+
final WorkingCapitalBreachRequest request = workingCapitalRequestFactory.defaultWorkingCapitalBreachRequest() //
109+
.breachFrequency(4) //
110+
.breachFrequencyType("MONTHS") //
111+
.breachAmountCalculationType("FLAT") //
112+
.breachAmount(new BigDecimal("7.89"));
113+
ok(() -> fineractFeignClient.workingCapitalBreaches().updateWorkingCapitalBreach(id, request));
114+
TestContext.GLOBAL.set(TestContextKey.WORKING_CAPITAL_BREACH_UPDATE_REQUEST, request);
115+
}
116+
117+
@Then("Check updated Breach has the following values")
118+
public void checkUpdatedBreachHasTheFollowingValues() {
119+
final Long id = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_ID);
120+
final WorkingCapitalBreachData data = ok(() -> fineractFeignClient.workingCapitalBreaches().retrieveWorkingCapitalBreach(id));
121+
final WorkingCapitalBreachRequest request = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_UPDATE_REQUEST);
122+
checkBreachData(request, data);
123+
}
124+
125+
@When("Admin deletes WC Breach With Values")
126+
public void adminDeletesWCBreachWithValues() {
127+
final Long id = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_ID);
128+
ok(() -> fineractFeignClient.workingCapitalBreaches().deleteWorkingCapitalBreach(id));
129+
}
130+
131+
@When("Admin deletes WC Breach With Values for update")
132+
public void adminDeletesWCBreachWithValuesForUpdate() {
133+
final Long id = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_ID_FOR_UPDATE);
134+
ok(() -> fineractFeignClient.workingCapitalBreaches().deleteWorkingCapitalBreach(id));
135+
}
136+
137+
@Then("Admin failed to create a new WC Breach for field {string} with invalid data {string} results with an error {}")
138+
public void createWCBreachWithInvalidDataFailed(final String fieldName, final String value, final String errorMessage) {
139+
final WorkingCapitalBreachRequest request = setWCBreachFieldValue(workingCapitalRequestFactory.defaultWorkingCapitalBreachRequest(),
140+
fieldName, value);
141+
checkCreateWCBreachWithInvalidDataFailure(request, errorMessage, 400);
142+
}
143+
144+
@Then("Admin failed to update WC Breach for field {string} with invalid data {string} results with an error {}")
145+
public void updateWCBreachWithInvalidDataFailed(final String fieldName, final String value, final String errorMessage) {
146+
Long id = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_ID_FOR_UPDATE);
147+
if (id == null) {
148+
adminCreatesWCBreachWithValuesForUpdate();
149+
id = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_ID_FOR_UPDATE);
150+
}
151+
final WorkingCapitalBreachRequest request = setWCBreachFieldValue(workingCapitalRequestFactory.defaultWorkingCapitalBreachRequest(),
152+
fieldName, value);
153+
checkUpdateWCBreachWithInvalidDataFailure(id, request, errorMessage, 400);
154+
}
155+
156+
@Then("Admin failed to delete WC Breach that is already deleted")
157+
public void adminDeleteWCBreachAlreadyDeletedFailure() {
158+
final Long id = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_ID_FOR_UPDATE);
159+
checkDeleteWCBreachNotFoundFailure(id);
160+
}
161+
162+
@Then("Admin failed to retrieve WC Breach that is already deleted")
163+
public void adminRetrieveWCBreachAlreadyDeletedFailure() {
164+
final Long id = TestContext.GLOBAL.get(TestContextKey.WORKING_CAPITAL_BREACH_ID_FOR_UPDATE);
165+
checkRetrieveWCBreachNotFoundFailure(id);
166+
}
167+
168+
@Then("Admin failed to delete WC Breach with id {int} that doesn't exist")
169+
public void adminDeleteWCBreachWithIncorrectIdFailure(final Integer id) {
170+
checkDeleteWCBreachNotFoundFailure(Long.valueOf(id));
171+
}
172+
173+
@Then("Admin failed to retrieve WC Breach with id {int} that is not found")
174+
public void adminRetrieveWCBreachWithIncorrectIdFailure(final Integer id) {
175+
checkRetrieveWCBreachNotFoundFailure(Long.valueOf(id));
176+
}
177+
178+
private void checkCreateWCBreachWithInvalidDataFailure(final WorkingCapitalBreachRequest request, final String errorMessage,
179+
final int errorCode) {
180+
final CallFailedRuntimeException exception = fail(
181+
() -> fineractFeignClient.workingCapitalBreaches().createWorkingCapitalBreach(request));
182+
assertThat(exception.getStatus()).as(ErrorMessageHelper.incorrectExpectedValueInResponse()).isEqualTo(errorCode);
183+
assertThat(exception.getDeveloperMessage()).contains(errorMessage);
184+
}
185+
186+
private void checkUpdateWCBreachWithInvalidDataFailure(final Long id, final WorkingCapitalBreachRequest request,
187+
final String errorMessage, final int errorCode) {
188+
final CallFailedRuntimeException exception = fail(
189+
() -> fineractFeignClient.workingCapitalBreaches().updateWorkingCapitalBreach(id, request));
190+
assertThat(exception.getStatus()).as(ErrorMessageHelper.incorrectExpectedValueInResponse()).isEqualTo(errorCode);
191+
assertThat(exception.getDeveloperMessage()).contains(errorMessage);
192+
}
193+
194+
private void checkDeleteWCBreachNotFoundFailure(final Long id) {
195+
final CallFailedRuntimeException exception = fail(
196+
() -> fineractFeignClient.workingCapitalBreaches().deleteWorkingCapitalBreach(id));
197+
assertThat(exception.getStatus()).isEqualTo(404);
198+
assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.workingCapitalBreachNotFoundFailure(id));
199+
}
200+
201+
private void checkRetrieveWCBreachNotFoundFailure(final Long id) {
202+
final CallFailedRuntimeException exception = fail(
203+
() -> fineractFeignClient.workingCapitalBreaches().retrieveWorkingCapitalBreach(id));
204+
assertThat(exception.getStatus()).isEqualTo(404);
205+
assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.workingCapitalBreachNotFoundFailure(id));
206+
}
207+
208+
private WorkingCapitalBreachRequest setWCBreachFieldValue(final WorkingCapitalBreachRequest request, final String fieldName,
209+
String fieldValue) {
210+
if ("null".equals(fieldValue)) {
211+
fieldValue = null;
212+
}
213+
final Integer intValue = fieldValue != null && "breachFrequency".equals(fieldName) ? Integer.valueOf(fieldValue) : null;
214+
final BigDecimal decimalValue = fieldValue != null && "breachAmount".equals(fieldName) ? new BigDecimal(fieldValue) : null;
215+
switch (fieldName) {
216+
case "breachFrequency":
217+
request.setBreachFrequency(intValue);
218+
break;
219+
case "breachFrequencyType":
220+
request.setBreachFrequencyType(fieldValue);
221+
break;
222+
case "breachAmountCalculationType":
223+
request.setBreachAmountCalculationType(fieldValue);
224+
break;
225+
case "breachAmount":
226+
request.setBreachAmount(decimalValue);
227+
break;
228+
default:
229+
break;
230+
}
231+
return request;
232+
}
233+
234+
private void checkBreachData(final WorkingCapitalBreachRequest request, final WorkingCapitalBreachData response) {
235+
final SoftAssertions assertions = new SoftAssertions();
236+
assertions.assertThat(response.getBreachFrequency()).isEqualTo(request.getBreachFrequency());
237+
assertions.assertThat(enumId(response.getBreachFrequencyType())).isEqualTo(request.getBreachFrequencyType());
238+
assertions.assertThat(enumId(response.getBreachAmountCalculationType())).isEqualTo(request.getBreachAmountCalculationType());
239+
assert response.getBreachAmount() != null;
240+
assertions.assertThat(response.getBreachAmount().compareTo(request.getBreachAmount())).isEqualTo(0);
241+
assertions.assertAll();
242+
}
243+
244+
private String enumId(final StringEnumOptionData option) {
245+
return option != null ? option.getId() : null;
246+
}
247+
}

0 commit comments

Comments
 (0)