1919package org .apache .fineract .test .stepdef .loan ;
2020
2121import static org .apache .fineract .client .feign .util .FeignCalls .ok ;
22+ import static org .assertj .core .api .Assertions .assertThat ;
2223
24+ import edu .umd .cs .findbugs .annotations .SuppressFBWarnings ;
25+ import io .cucumber .datatable .DataTable ;
2326import io .cucumber .java .en .Then ;
2427import io .cucumber .java .en .When ;
25- 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 ;
32+ import java .util .List ;
2633import java .util .Map ;
34+ import java .util .stream .Collectors ;
2735import org .apache .fineract .client .feign .FineractFeignClient ;
36+ import org .apache .fineract .client .models .LoanScheduleData ;
37+ import org .apache .fineract .client .models .LoanSchedulePeriodData ;
2838import org .apache .fineract .client .models .PostLoansLoanIdTransactionsRequest ;
2939import org .apache .fineract .client .models .PostLoansLoanIdTransactionsResponse ;
3040import org .apache .fineract .client .models .PostLoansResponse ;
3141import org .apache .fineract .test .factory .LoanRequestFactory ;
42+ import org .apache .fineract .test .helper .ErrorMessageHelper ;
43+ import org .apache .fineract .test .helper .Utils ;
3244import org .apache .fineract .test .messaging .EventAssertion ;
3345import org .apache .fineract .test .messaging .event .loan .LoanReAmortizeEvent ;
3446import org .apache .fineract .test .stepdef .AbstractStepDef ;
3749
3850public class LoanReAmortizationStepDef extends AbstractStepDef {
3951
52+ private static final String DATE_FORMAT = "dd MMMM yyyy" ;
53+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter .ofPattern (DATE_FORMAT );
54+
4055 @ Autowired
4156 private FineractFeignClient fineractClient ;
4257
4358 @ Autowired
4459 private EventAssertion eventAssertion ;
4560
4661 @ When ("When Admin creates a Loan re-amortization transaction on current business date" )
47- public void createLoanReAmortization () throws IOException {
62+ public void createLoanReAmortization () {
4863 PostLoansResponse loanResponse = testContext ().get (TestContextKey .LOAN_CREATE_RESPONSE );
4964 long loanId = loanResponse .getLoanId ();
5065
@@ -56,7 +71,7 @@ public void createLoanReAmortization() throws IOException {
5671 }
5772
5873 @ When ("When Admin creates a Loan re-amortization transaction on current business date by loan external ID" )
59- public void createLoanReAmortizationByLoanExternalId () throws IOException {
74+ public void createLoanReAmortizationByLoanExternalId () {
6075 PostLoansResponse loanResponse = testContext ().get (TestContextKey .LOAN_CREATE_RESPONSE );
6176 String loanExternalId = loanResponse .getResourceExternalId ();
6277
@@ -68,7 +83,7 @@ public void createLoanReAmortizationByLoanExternalId() throws IOException {
6883 }
6984
7085 @ When ("When Admin undo Loan re-amortization transaction on current business date" )
71- public void undoLoanReAmortization () throws IOException {
86+ public void undoLoanReAmortization () {
7287 PostLoansResponse loanResponse = testContext ().get (TestContextKey .LOAN_CREATE_RESPONSE );
7388 long loanId = loanResponse .getLoanId ();
7489
@@ -84,4 +99,157 @@ public void checkLoanReAmortizeBusinessEventCreated() {
8499
85100 eventAssertion .assertEventRaised (LoanReAmortizeEvent .class , loanId );
86101 }
102+
103+ @ When ("Admin creates a Loan re-amortization preview by Loan external ID with the following data:" )
104+ public void createReAmortizedPreviewByLoanExternalId (final DataTable table ) {
105+ final LoanScheduleData response = reAmortizedPreviewByLoanExternalId (table );
106+ testContext ().set (TestContextKey .LOAN_REAMORTIZATION_PREVIEW_RESPONSE , response );
107+ }
108+
109+ @ Then ("Loan Re-Amortization Repayment schedule preview has the following data in Total row:" )
110+ public void loanRepaymentDScheduleAmountCheck (final DataTable table ) {
111+ final List <List <String >> data = table .asLists ();
112+ final List <String > header = data .get (0 );
113+ final List <String > expectedValues = data .get (1 );
114+ final LoanScheduleData scheduleResponse = testContext ().get (TestContextKey .LOAN_REAMORTIZATION_PREVIEW_RESPONSE );
115+ validateRepaymentScheduleTotal (header , scheduleResponse , expectedValues );
116+ }
117+
118+ @ Then ("Loan Re-Amortization Repayment schedule preview has {int} periods, with the following data for periods:" )
119+ public void loanRepaymentSchedulePreviewPeriodsCheck (final int linesExpected , final DataTable table ) {
120+ final LoanScheduleData scheduleResponse = testContext ().get (TestContextKey .LOAN_REAMORTIZATION_PREVIEW_RESPONSE );
121+ final List <LoanSchedulePeriodData > repaymentPeriods = scheduleResponse .getPeriods ();
122+
123+ final PostLoansResponse loanResponse = testContext ().get (TestContextKey .LOAN_CREATE_RESPONSE );
124+ final String resourceId = String .valueOf (loanResponse .getLoanId ());
125+ final List <List <String >> data = table .asLists ();
126+ final int nrLines = data .size ();
127+ final int linesActual = (int ) repaymentPeriods .stream ().filter (r -> r .getPeriod () != null ).count ();
128+
129+ for (int i = 1 ; i < nrLines ; i ++) {
130+ List <String > expectedValues = data .get (i );
131+ String dueDateExpected = expectedValues .get (2 );
132+
133+ List <List <String >> actualValuesList = repaymentPeriods .stream ()
134+ .filter (r -> dueDateExpected .equals (FORMATTER .format (r .getDueDate ())))
135+ .map (r -> fetchValuesOfRepaymentSchedule (data .get (0 ), r )).collect (Collectors .toList ());
136+
137+ boolean containsExpectedValues = actualValuesList .stream ().anyMatch (actualValues -> actualValues .equals (expectedValues ));
138+ assertThat (containsExpectedValues )
139+ .as (ErrorMessageHelper .wrongValueInLineInRepaymentSchedule (resourceId , i , actualValuesList , expectedValues )).isTrue ();
140+
141+ assertThat (linesActual ).as (ErrorMessageHelper .wrongNumberOfLinesInRepaymentSchedule (resourceId , linesActual , linesExpected ))
142+ .isEqualTo (linesExpected );
143+ }
144+ }
145+
146+ private LoanScheduleData reAmortizedPreviewByLoanExternalId (final DataTable table ) {
147+ final PostLoansResponse loanResponse = testContext ().get (TestContextKey .LOAN_CREATE_RESPONSE );
148+ final String loanExternalId = loanResponse .getResourceExternalId ();
149+
150+ final List <String > data = table .asLists ().get (1 );
151+ final String reAmortizationInterestHandling = data .getFirst ();
152+
153+ final Map <String , Object > queryParams = Map .of ("reAmortizationInterestHandling" , reAmortizationInterestHandling );
154+ return ok (() -> fineractClient .loanTransactions ().previewReAmortizationSchedule1 (loanExternalId , queryParams ));
155+ }
156+
157+ @ SuppressFBWarnings ("SF_SWITCH_NO_DEFAULT" )
158+ private void validateRepaymentScheduleTotal (final List <String > header , final LoanScheduleData repaymentSchedule ,
159+ final List <String > expectedAmounts ) {
160+ Double paidActual = 0.0 ;
161+ final List <LoanSchedulePeriodData > periods = repaymentSchedule .getPeriods ();
162+ for (LoanSchedulePeriodData period : periods ) {
163+ if (null != period .getTotalPaidForPeriod ()) {
164+ paidActual += period .getTotalPaidForPeriod ().doubleValue ();
165+ }
166+ }
167+ final BigDecimal paidActualBd = new BigDecimal (paidActual ).setScale (2 , RoundingMode .HALF_DOWN );
168+
169+ for (int i = 0 ; i < header .size (); i ++) {
170+ final String headerName = header .get (i );
171+ final String expectedValue = expectedAmounts .get (i );
172+ switch (headerName ) {
173+ case "Principal due" -> assertThat (repaymentSchedule .getTotalPrincipalExpected ())//
174+ .as (ErrorMessageHelper .wrongAmountInRepaymentSchedulePrincipal (
175+ repaymentSchedule .getTotalPrincipalExpected ().doubleValue (), Double .valueOf (expectedValue )))//
176+ .isEqualByComparingTo (new BigDecimal (expectedValue ));//
177+ case "Interest" -> assertThat (repaymentSchedule .getTotalInterestCharged ())//
178+ .as (ErrorMessageHelper .wrongAmountInRepaymentScheduleInterest (
179+ repaymentSchedule .getTotalInterestCharged ().doubleValue (), Double .valueOf (expectedValue )))//
180+ .isEqualByComparingTo (new BigDecimal (expectedValue ));//
181+ case "Fees" -> assertThat (repaymentSchedule .getTotalFeeChargesCharged ())//
182+ .as (ErrorMessageHelper .wrongAmountInRepaymentScheduleFees (
183+ repaymentSchedule .getTotalFeeChargesCharged ().doubleValue (), Double .valueOf (expectedValue )))//
184+ .isEqualByComparingTo (new BigDecimal (expectedValue ));//
185+ case "Penalties" -> assertThat (repaymentSchedule .getTotalPenaltyChargesCharged ())//
186+ .as (ErrorMessageHelper .wrongAmountInRepaymentSchedulePenalties (
187+ repaymentSchedule .getTotalPenaltyChargesCharged ().doubleValue (), Double .valueOf (expectedValue )))//
188+ .isEqualByComparingTo (new BigDecimal (expectedValue ));//
189+ case "Due" -> assertThat (repaymentSchedule .getTotalRepaymentExpected ())//
190+ .as (ErrorMessageHelper .wrongAmountInRepaymentScheduleDue (
191+ repaymentSchedule .getTotalRepaymentExpected ().doubleValue (), Double .valueOf (expectedValue )))//
192+ .isEqualByComparingTo (new BigDecimal (expectedValue ));//
193+ case "Paid" -> assertThat (paidActualBd )//
194+ .as (ErrorMessageHelper .wrongAmountInRepaymentSchedulePaid (paidActualBd .doubleValue (),
195+ Double .valueOf (expectedValue )))//
196+ .isEqualByComparingTo (new BigDecimal (expectedValue ));//
197+ case "In advance" -> assertThat (repaymentSchedule .getTotalPaidInAdvance ())//
198+ .as (ErrorMessageHelper .wrongAmountInRepaymentScheduleInAdvance (
199+ repaymentSchedule .getTotalPaidInAdvance ().doubleValue (), Double .valueOf (expectedValue )))//
200+ .isEqualByComparingTo (new BigDecimal (expectedValue ));//
201+ case "Late" -> assertThat (repaymentSchedule .getTotalPaidLate ())//
202+ .as (ErrorMessageHelper .wrongAmountInRepaymentScheduleLate (repaymentSchedule .getTotalPaidLate ().doubleValue (),
203+ Double .valueOf (expectedValue )))//
204+ .isEqualByComparingTo (new BigDecimal (expectedValue ));//
205+ case "Waived" -> assertThat (repaymentSchedule .getTotalWaived ())//
206+ .as (ErrorMessageHelper .wrongAmountInRepaymentScheduleWaived (repaymentSchedule .getTotalWaived ().doubleValue (),
207+ Double .valueOf (expectedValue )))//
208+ .isEqualByComparingTo (new BigDecimal (expectedValue ));//
209+ case "Outstanding" -> assertThat (repaymentSchedule .getTotalOutstanding ())//
210+ .as (ErrorMessageHelper .wrongAmountInRepaymentScheduleOutstanding (
211+ repaymentSchedule .getTotalOutstanding ().doubleValue (), Double .valueOf (expectedValue )))//
212+ .isEqualByComparingTo (new BigDecimal (expectedValue ));//
213+ }
214+ }
215+ }
216+
217+ private List <String > fetchValuesOfRepaymentSchedule (final List <String > header , final LoanSchedulePeriodData repaymentPeriod ) {
218+ final List <String > actualValues = new ArrayList <>();
219+ for (String headerName : header ) {
220+ switch (headerName ) {
221+ case "Nr" -> actualValues .add (repaymentPeriod .getPeriod () == null ? null : String .valueOf (repaymentPeriod .getPeriod ()));
222+ case "Days" ->
223+ actualValues .add (repaymentPeriod .getDaysInPeriod () == null ? null : String .valueOf (repaymentPeriod .getDaysInPeriod ()));
224+ case "Date" ->
225+ actualValues .add (repaymentPeriod .getDueDate () == null ? null : FORMATTER .format (repaymentPeriod .getDueDate ()));
226+ case "Paid date" -> actualValues .add (repaymentPeriod .getObligationsMetOnDate () == null ? null
227+ : FORMATTER .format (repaymentPeriod .getObligationsMetOnDate ()));
228+ case "Balance of loan" -> actualValues .add (repaymentPeriod .getPrincipalLoanBalanceOutstanding () == null ? null
229+ : new Utils .DoubleFormatter (repaymentPeriod .getPrincipalLoanBalanceOutstanding ().doubleValue ()).format ());
230+ case "Principal due" -> actualValues .add (repaymentPeriod .getPrincipalDue () == null ? null
231+ : new Utils .DoubleFormatter (repaymentPeriod .getPrincipalDue ().doubleValue ()).format ());
232+ case "Interest" -> actualValues .add (repaymentPeriod .getInterestDue () == null ? null
233+ : new Utils .DoubleFormatter (repaymentPeriod .getInterestDue ().doubleValue ()).format ());
234+ case "Fees" -> actualValues .add (repaymentPeriod .getFeeChargesDue () == null ? null
235+ : new Utils .DoubleFormatter (repaymentPeriod .getFeeChargesDue ().doubleValue ()).format ());
236+ case "Penalties" -> actualValues .add (repaymentPeriod .getPenaltyChargesDue () == null ? null
237+ : new Utils .DoubleFormatter (repaymentPeriod .getPenaltyChargesDue ().doubleValue ()).format ());
238+ case "Due" -> actualValues .add (repaymentPeriod .getTotalDueForPeriod () == null ? null
239+ : new Utils .DoubleFormatter (repaymentPeriod .getTotalDueForPeriod ().doubleValue ()).format ());
240+ case "Paid" -> actualValues .add (repaymentPeriod .getTotalPaidForPeriod () == null ? null
241+ : new Utils .DoubleFormatter (repaymentPeriod .getTotalPaidForPeriod ().doubleValue ()).format ());
242+ case "In advance" -> actualValues .add (repaymentPeriod .getTotalPaidInAdvanceForPeriod () == null ? null
243+ : new Utils .DoubleFormatter (repaymentPeriod .getTotalPaidInAdvanceForPeriod ().doubleValue ()).format ());
244+ case "Late" -> actualValues .add (repaymentPeriod .getTotalPaidLateForPeriod () == null ? null
245+ : new Utils .DoubleFormatter (repaymentPeriod .getTotalPaidLateForPeriod ().doubleValue ()).format ());
246+ case "Waived" -> actualValues .add (repaymentPeriod .getTotalWaivedForPeriod () == null ? null
247+ : new Utils .DoubleFormatter (repaymentPeriod .getTotalWaivedForPeriod ().doubleValue ()).format ());
248+ case "Outstanding" -> actualValues .add (repaymentPeriod .getTotalOutstandingForPeriod () == null ? null
249+ : new Utils .DoubleFormatter (repaymentPeriod .getTotalOutstandingForPeriod ().doubleValue ()).format ());
250+ default -> throw new IllegalStateException (String .format ("Header name %s cannot be found" , headerName ));
251+ }
252+ }
253+ return actualValues ;
254+ }
87255}
0 commit comments