| 
18 | 18 |  */  | 
19 | 19 | package org.apache.fineract.test.stepdef.loan;  | 
20 | 20 | 
 
  | 
 | 21 | +import static org.assertj.core.api.Assertions.assertThat;  | 
 | 22 | + | 
 | 23 | +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;  | 
21 | 24 | import io.cucumber.datatable.DataTable;  | 
22 | 25 | import io.cucumber.java.en.Then;  | 
23 | 26 | import io.cucumber.java.en.When;  | 
24 | 27 | import java.io.IOException;  | 
 | 28 | +import java.math.BigDecimal;  | 
 | 29 | +import java.math.RoundingMode;  | 
 | 30 | +import java.time.format.DateTimeFormatter;  | 
 | 31 | +import java.util.ArrayList;  | 
25 | 32 | import java.util.List;  | 
 | 33 | +import java.util.stream.Collectors;  | 
26 | 34 | import lombok.extern.slf4j.Slf4j;  | 
 | 35 | +import org.apache.fineract.client.models.LoanScheduleData;  | 
 | 36 | +import org.apache.fineract.client.models.LoanSchedulePeriodData;  | 
27 | 37 | import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;  | 
28 | 38 | import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;  | 
29 | 39 | import org.apache.fineract.client.models.PostLoansResponse;  | 
30 | 40 | import org.apache.fineract.client.services.LoanTransactionsApi;  | 
31 | 41 | import org.apache.fineract.test.factory.LoanRequestFactory;  | 
32 | 42 | import org.apache.fineract.test.helper.ErrorHelper;  | 
 | 43 | +import org.apache.fineract.test.helper.ErrorMessageHelper;  | 
 | 44 | +import org.apache.fineract.test.helper.Utils;  | 
33 | 45 | import org.apache.fineract.test.messaging.EventAssertion;  | 
34 | 46 | import org.apache.fineract.test.messaging.event.loan.LoanReAgeEvent;  | 
35 | 47 | import org.apache.fineract.test.stepdef.AbstractStepDef;  | 
 | 
40 | 52 | @Slf4j  | 
41 | 53 | public class LoanReAgingStepDef extends AbstractStepDef {  | 
42 | 54 | 
 
  | 
 | 55 | +    private static final String DATE_FORMAT = "dd MMMM yyyy";  | 
 | 56 | +    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT);  | 
 | 57 | + | 
43 | 58 |     @Autowired  | 
44 | 59 |     private LoanTransactionsApi loanTransactionsApi;  | 
45 | 60 | 
 
  | 
@@ -112,4 +127,198 @@ public void checkLoanReAmortizeBusinessEventCreated() {  | 
112 | 127 | 
 
  | 
113 | 128 |         eventAssertion.assertEventRaised(LoanReAgeEvent.class, loanId);  | 
114 | 129 |     }  | 
 | 130 | + | 
 | 131 | +    @When("Admin creates a Loan re-aging preview with the following data:")  | 
 | 132 | +    public void createReAgingPreview(DataTable table) throws IOException {  | 
 | 133 | +        Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);  | 
 | 134 | +        long loanId = loanResponse.body().getLoanId();  | 
 | 135 | + | 
 | 136 | +        List<String> data = table.asLists().get(1);  | 
 | 137 | +        int frequencyNumber = Integer.parseInt(data.get(0));  | 
 | 138 | +        String frequencyType = data.get(1);  | 
 | 139 | +        String startDate = data.get(2);  | 
 | 140 | +        int numberOfInstallments = Integer.parseInt(data.get(3));  | 
 | 141 | + | 
 | 142 | +        PostLoansLoanIdTransactionsRequest reAgingRequest = LoanRequestFactory//  | 
 | 143 | +                .defaultReAgingRequest()//  | 
 | 144 | +                .frequencyNumber(frequencyNumber)//  | 
 | 145 | +                .frequencyType(frequencyType)//  | 
 | 146 | +                .startDate(startDate)//  | 
 | 147 | +                .numberOfInstallments(numberOfInstallments);//  | 
 | 148 | + | 
 | 149 | +        Response<LoanScheduleData> response = loanTransactionsApi.previewReAgeSchedule(loanId, reAgingRequest).execute();  | 
 | 150 | +        ErrorHelper.checkSuccessfulApiCall(response);  | 
 | 151 | +        testContext().set(TestContextKey.LOAN_REAGING_PREVIEW_RESPONSE, response);  | 
 | 152 | + | 
 | 153 | +        log.info(  | 
 | 154 | +                "Re-aging preview created for loan ID: {} with parameters: frequencyNumber={}, frequencyType={}, startDate={}, numberOfInstallments={}",  | 
 | 155 | +                loanId, frequencyNumber, frequencyType, startDate, numberOfInstallments);  | 
 | 156 | +    }  | 
 | 157 | + | 
 | 158 | +    @When("Admin creates a Loan re-aging preview by Loan external ID with the following data:")  | 
 | 159 | +    public void createReAgingPreviewByLoanExternalId(DataTable table) throws IOException {  | 
 | 160 | +        Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);  | 
 | 161 | +        String loanExternalId = loanResponse.body().getResourceExternalId();  | 
 | 162 | + | 
 | 163 | +        List<String> data = table.asLists().get(1);  | 
 | 164 | +        int frequencyNumber = Integer.parseInt(data.get(0));  | 
 | 165 | +        String frequencyType = data.get(1);  | 
 | 166 | +        String startDate = data.get(2);  | 
 | 167 | +        int numberOfInstallments = Integer.parseInt(data.get(3));  | 
 | 168 | + | 
 | 169 | +        PostLoansLoanIdTransactionsRequest reAgingRequest = LoanRequestFactory//  | 
 | 170 | +                .defaultReAgingRequest()//  | 
 | 171 | +                .frequencyNumber(frequencyNumber)//  | 
 | 172 | +                .frequencyType(frequencyType)//  | 
 | 173 | +                .startDate(startDate)//  | 
 | 174 | +                .numberOfInstallments(numberOfInstallments);//  | 
 | 175 | + | 
 | 176 | +        Response<LoanScheduleData> response = loanTransactionsApi.previewReAgeSchedule1(loanExternalId, reAgingRequest).execute();  | 
 | 177 | +        ErrorHelper.checkSuccessfulApiCall(response);  | 
 | 178 | +        testContext().set(TestContextKey.LOAN_REAGING_PREVIEW_RESPONSE, response);  | 
 | 179 | + | 
 | 180 | +        log.info(  | 
 | 181 | +                "Re-aging preview created for loan external ID: {} with parameters: frequencyNumber={}, frequencyType={}, startDate={}, numberOfInstallments={}",  | 
 | 182 | +                loanExternalId, frequencyNumber, frequencyType, startDate, numberOfInstallments);  | 
 | 183 | +    }  | 
 | 184 | + | 
 | 185 | +    @Then("Loan Repayment schedule preview has {int} periods, with the following data for periods:")  | 
 | 186 | +    public void loanRepaymentSchedulePreviewPeriodsCheck(int linesExpected, DataTable table) {  | 
 | 187 | +        Response<LoanScheduleData> scheduleResponse = testContext().get(TestContextKey.LOAN_REAGING_PREVIEW_RESPONSE);  | 
 | 188 | + | 
 | 189 | +        List<LoanSchedulePeriodData> repaymentPeriods = scheduleResponse.body().getPeriods();  | 
 | 190 | + | 
 | 191 | +        Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);  | 
 | 192 | +        String resourceId = String.valueOf(loanResponse.body().getLoanId());  | 
 | 193 | + | 
 | 194 | +        List<List<String>> data = table.asLists();  | 
 | 195 | +        int nrLines = data.size();  | 
 | 196 | +        int linesActual = (int) repaymentPeriods.stream().filter(r -> r.getPeriod() != null).count();  | 
 | 197 | +        for (int i = 1; i < nrLines; i++) {  | 
 | 198 | +            List<String> expectedValues = data.get(i);  | 
 | 199 | +            String dueDateExpected = expectedValues.get(2);  | 
 | 200 | + | 
 | 201 | +            List<List<String>> actualValuesList = repaymentPeriods.stream()  | 
 | 202 | +                    .filter(r -> dueDateExpected.equals(FORMATTER.format(r.getDueDate())))  | 
 | 203 | +                    .map(r -> fetchValuesOfRepaymentSchedule(data.get(0), r)).collect(Collectors.toList());  | 
 | 204 | + | 
 | 205 | +            boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));  | 
 | 206 | +            assertThat(containsExpectedValues)  | 
 | 207 | +                    .as(ErrorMessageHelper.wrongValueInLineInRepaymentSchedule(resourceId, i, actualValuesList, expectedValues)).isTrue();  | 
 | 208 | + | 
 | 209 | +            assertThat(linesActual).as(ErrorMessageHelper.wrongNumberOfLinesInRepaymentSchedule(resourceId, linesActual, linesExpected))  | 
 | 210 | +                    .isEqualTo(linesExpected);  | 
 | 211 | +        }  | 
 | 212 | +    }  | 
 | 213 | + | 
 | 214 | +    @Then("Loan Repayment schedule preview has the following data in Total row:")  | 
 | 215 | +    public void loanRepaymentScheduleAmountCheck(DataTable table) {  | 
 | 216 | +        List<List<String>> data = table.asLists();  | 
 | 217 | +        List<String> header = data.get(0);  | 
 | 218 | +        List<String> expectedValues = data.get(1);  | 
 | 219 | +        Response<LoanScheduleData> scheduleResponse = testContext().get(TestContextKey.LOAN_REAGING_PREVIEW_RESPONSE);  | 
 | 220 | +        validateRepaymentScheduleTotal(header, scheduleResponse.body(), expectedValues);  | 
 | 221 | +    }  | 
 | 222 | + | 
 | 223 | +    private List<String> fetchValuesOfRepaymentSchedule(List<String> header, LoanSchedulePeriodData repaymentPeriod) {  | 
 | 224 | +        List<String> actualValues = new ArrayList<>();  | 
 | 225 | +        for (String headerName : header) {  | 
 | 226 | +            switch (headerName) {  | 
 | 227 | +                case "Nr" -> actualValues.add(repaymentPeriod.getPeriod() == null ? null : String.valueOf(repaymentPeriod.getPeriod()));  | 
 | 228 | +                case "Days" ->  | 
 | 229 | +                    actualValues.add(repaymentPeriod.getDaysInPeriod() == null ? null : String.valueOf(repaymentPeriod.getDaysInPeriod()));  | 
 | 230 | +                case "Date" ->  | 
 | 231 | +                    actualValues.add(repaymentPeriod.getDueDate() == null ? null : FORMATTER.format(repaymentPeriod.getDueDate()));  | 
 | 232 | +                case "Paid date" -> actualValues.add(repaymentPeriod.getObligationsMetOnDate() == null ? null  | 
 | 233 | +                        : FORMATTER.format(repaymentPeriod.getObligationsMetOnDate()));  | 
 | 234 | +                case "Balance of loan" -> actualValues.add(repaymentPeriod.getPrincipalLoanBalanceOutstanding() == null ? null  | 
 | 235 | +                        : new Utils.DoubleFormatter(repaymentPeriod.getPrincipalLoanBalanceOutstanding().doubleValue()).format());  | 
 | 236 | +                case "Principal due" -> actualValues.add(repaymentPeriod.getPrincipalDue() == null ? null  | 
 | 237 | +                        : new Utils.DoubleFormatter(repaymentPeriod.getPrincipalDue().doubleValue()).format());  | 
 | 238 | +                case "Interest" -> actualValues.add(repaymentPeriod.getInterestDue() == null ? null  | 
 | 239 | +                        : new Utils.DoubleFormatter(repaymentPeriod.getInterestDue().doubleValue()).format());  | 
 | 240 | +                case "Fees" -> actualValues.add(repaymentPeriod.getFeeChargesDue() == null ? null  | 
 | 241 | +                        : new Utils.DoubleFormatter(repaymentPeriod.getFeeChargesDue().doubleValue()).format());  | 
 | 242 | +                case "Penalties" -> actualValues.add(repaymentPeriod.getPenaltyChargesDue() == null ? null  | 
 | 243 | +                        : new Utils.DoubleFormatter(repaymentPeriod.getPenaltyChargesDue().doubleValue()).format());  | 
 | 244 | +                case "Due" -> actualValues.add(repaymentPeriod.getTotalDueForPeriod() == null ? null  | 
 | 245 | +                        : new Utils.DoubleFormatter(repaymentPeriod.getTotalDueForPeriod().doubleValue()).format());  | 
 | 246 | +                case "Paid" -> actualValues.add(repaymentPeriod.getTotalPaidForPeriod() == null ? null  | 
 | 247 | +                        : new Utils.DoubleFormatter(repaymentPeriod.getTotalPaidForPeriod().doubleValue()).format());  | 
 | 248 | +                case "In advance" -> actualValues.add(repaymentPeriod.getTotalPaidInAdvanceForPeriod() == null ? null  | 
 | 249 | +                        : new Utils.DoubleFormatter(repaymentPeriod.getTotalPaidInAdvanceForPeriod().doubleValue()).format());  | 
 | 250 | +                case "Late" -> actualValues.add(repaymentPeriod.getTotalPaidLateForPeriod() == null ? null  | 
 | 251 | +                        : new Utils.DoubleFormatter(repaymentPeriod.getTotalPaidLateForPeriod().doubleValue()).format());  | 
 | 252 | +                case "Waived" -> actualValues.add(repaymentPeriod.getTotalWaivedForPeriod() == null ? null  | 
 | 253 | +                        : new Utils.DoubleFormatter(repaymentPeriod.getTotalWaivedForPeriod().doubleValue()).format());  | 
 | 254 | +                case "Outstanding" -> actualValues.add(repaymentPeriod.getTotalOutstandingForPeriod() == null ? null  | 
 | 255 | +                        : new Utils.DoubleFormatter(repaymentPeriod.getTotalOutstandingForPeriod().doubleValue()).format());  | 
 | 256 | +                default -> throw new IllegalStateException(String.format("Header name %s cannot be found", headerName));  | 
 | 257 | +            }  | 
 | 258 | +        }  | 
 | 259 | +        return actualValues;  | 
 | 260 | +    }  | 
 | 261 | + | 
 | 262 | +    @SuppressFBWarnings("SF_SWITCH_NO_DEFAULT")  | 
 | 263 | +    private List<String> validateRepaymentScheduleTotal(List<String> header, LoanScheduleData repaymentSchedule,  | 
 | 264 | +            List<String> expectedAmounts) {  | 
 | 265 | +        List<String> actualValues = new ArrayList<>();  | 
 | 266 | +        Double paidActual = 0.0;  | 
 | 267 | +        List<LoanSchedulePeriodData> periods = repaymentSchedule.getPeriods();  | 
 | 268 | +        for (LoanSchedulePeriodData period : periods) {  | 
 | 269 | +            if (null != period.getTotalPaidForPeriod()) {  | 
 | 270 | +                paidActual += period.getTotalPaidForPeriod().doubleValue();  | 
 | 271 | +            }  | 
 | 272 | +        }  | 
 | 273 | +        BigDecimal paidActualBd = new BigDecimal(paidActual).setScale(2, RoundingMode.HALF_DOWN);  | 
 | 274 | + | 
 | 275 | +        for (int i = 0; i < header.size(); i++) {  | 
 | 276 | +            String headerName = header.get(i);  | 
 | 277 | +            String expectedValue = expectedAmounts.get(i);  | 
 | 278 | +            switch (headerName) {  | 
 | 279 | +                case "Principal due" -> assertThat(repaymentSchedule.getTotalPrincipalExpected().doubleValue())//  | 
 | 280 | +                        .as(ErrorMessageHelper.wrongAmountInRepaymentSchedulePrincipal(  | 
 | 281 | +                                repaymentSchedule.getTotalPrincipalExpected().doubleValue(), Double.valueOf(expectedValue)))//  | 
 | 282 | +                        .isEqualTo(Double.valueOf(expectedValue));//  | 
 | 283 | +                case "Interest" -> assertThat(repaymentSchedule.getTotalInterestCharged().doubleValue())//  | 
 | 284 | +                        .as(ErrorMessageHelper.wrongAmountInRepaymentScheduleInterest(  | 
 | 285 | +                                repaymentSchedule.getTotalInterestCharged().doubleValue(), Double.valueOf(expectedValue)))//  | 
 | 286 | +                        .isEqualTo(Double.valueOf(expectedValue));//  | 
 | 287 | +                case "Fees" -> assertThat(repaymentSchedule.getTotalFeeChargesCharged().doubleValue())//  | 
 | 288 | +                        .as(ErrorMessageHelper.wrongAmountInRepaymentScheduleFees(  | 
 | 289 | +                                repaymentSchedule.getTotalFeeChargesCharged().doubleValue(), Double.valueOf(expectedValue)))//  | 
 | 290 | +                        .isEqualTo(Double.valueOf(expectedValue));//  | 
 | 291 | +                case "Penalties" -> assertThat(repaymentSchedule.getTotalPenaltyChargesCharged().doubleValue())//  | 
 | 292 | +                        .as(ErrorMessageHelper.wrongAmountInRepaymentSchedulePenalties(  | 
 | 293 | +                                repaymentSchedule.getTotalPenaltyChargesCharged().doubleValue(), Double.valueOf(expectedValue)))//  | 
 | 294 | +                        .isEqualTo(Double.valueOf(expectedValue));//  | 
 | 295 | +                case "Due" -> assertThat(repaymentSchedule.getTotalRepaymentExpected().doubleValue())//  | 
 | 296 | +                        .as(ErrorMessageHelper.wrongAmountInRepaymentScheduleDue(  | 
 | 297 | +                                repaymentSchedule.getTotalRepaymentExpected().doubleValue(), Double.valueOf(expectedValue)))//  | 
 | 298 | +                        .isEqualTo(Double.valueOf(expectedValue));//  | 
 | 299 | +                case "Paid" -> assertThat(paidActualBd.doubleValue())//  | 
 | 300 | +                        .as(ErrorMessageHelper.wrongAmountInRepaymentSchedulePaid(paidActualBd.doubleValue(),  | 
 | 301 | +                                Double.valueOf(expectedValue)))//  | 
 | 302 | +                        .isEqualTo(Double.valueOf(expectedValue));//  | 
 | 303 | +                case "In advance" -> assertThat(repaymentSchedule.getTotalPaidInAdvance().doubleValue())//  | 
 | 304 | +                        .as(ErrorMessageHelper.wrongAmountInRepaymentScheduleInAdvance(  | 
 | 305 | +                                repaymentSchedule.getTotalPaidInAdvance().doubleValue(), Double.valueOf(expectedValue)))//  | 
 | 306 | +                        .isEqualTo(Double.valueOf(expectedValue));//  | 
 | 307 | +                case "Late" -> assertThat(repaymentSchedule.getTotalPaidLate().doubleValue())//  | 
 | 308 | +                        .as(ErrorMessageHelper.wrongAmountInRepaymentScheduleLate(repaymentSchedule.getTotalPaidLate().doubleValue(),  | 
 | 309 | +                                Double.valueOf(expectedValue)))//  | 
 | 310 | +                        .isEqualTo(Double.valueOf(expectedValue));//  | 
 | 311 | +                case "Waived" -> assertThat(repaymentSchedule.getTotalWaived().doubleValue())//  | 
 | 312 | +                        .as(ErrorMessageHelper.wrongAmountInRepaymentScheduleWaived(repaymentSchedule.getTotalWaived().doubleValue(),  | 
 | 313 | +                                Double.valueOf(expectedValue)))//  | 
 | 314 | +                        .isEqualTo(Double.valueOf(expectedValue));//  | 
 | 315 | +                case "Outstanding" -> assertThat(repaymentSchedule.getTotalOutstanding().doubleValue())//  | 
 | 316 | +                        .as(ErrorMessageHelper.wrongAmountInRepaymentScheduleOutstanding(  | 
 | 317 | +                                repaymentSchedule.getTotalOutstanding().doubleValue(), Double.valueOf(expectedValue)))//  | 
 | 318 | +                        .isEqualTo(Double.valueOf(expectedValue));//  | 
 | 319 | +            }  | 
 | 320 | +        }  | 
 | 321 | +        return actualValues;  | 
 | 322 | +    }  | 
 | 323 | + | 
115 | 324 | }  | 
0 commit comments