diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalBatchApiStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalBatchApiStepDef.java index bc17ecd662d..572550b0949 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalBatchApiStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalBatchApiStepDef.java @@ -498,8 +498,9 @@ private List extractLoanFieldValues(GetWorkingCapitalLoansLoanIdResponse final List values = new ArrayList<>(); for (final String field : fields) { switch (field) { - case "product.name" -> values.add(loan.getProduct() == null ? null : loan.getProduct().getName()); - case "submittedOnDate" -> values.add(loan.getSubmittedOnDate() == null ? null : loan.getSubmittedOnDate().toString()); + case "product.name" -> values.add(loan.getLoanProductName()); + case "submittedOnDate" -> values.add(loan.getTimeline() == null || loan.getTimeline().getSubmittedOnDate() == null ? null + : loan.getTimeline().getSubmittedOnDate().toString()); case "expectedDisbursementDate" -> values.add(loan.getDisbursementDetails() == null || loan.getDisbursementDetails().isEmpty() ? null : loan.getDisbursementDetails().getFirst().getExpectedDisbursementDate().toString()); @@ -512,14 +513,14 @@ private List extractLoanFieldValues(GetWorkingCapitalLoansLoanIdResponse : new Utils.DoubleFormatter(loan.getApprovedPrincipal().doubleValue()).format()); case "totalPaymentVolume" -> values.add(loan.getTotalPaymentVolume() == null ? null : new Utils.DoubleFormatter(loan.getTotalPaymentVolume().doubleValue()).format()); - case "periodPaymentRate" -> values.add(loan.getPeriodPaymentRate() == null ? null - : new Utils.DoubleFormatter(loan.getPeriodPaymentRate().doubleValue()).format()); - case "discount" -> - values.add(loan.getDiscount() == null ? "null" : new Utils.DoubleFormatter(loan.getDiscount().doubleValue()).format()); - case "discountProposed" -> values.add(loan.getDiscountProposed() == null ? "null" - : new Utils.DoubleFormatter(loan.getDiscountProposed().doubleValue()).format()); - case "discountApproved" -> values.add(loan.getDiscountApproved() == null ? "null" - : new Utils.DoubleFormatter(loan.getDiscountApproved().doubleValue()).format()); + case "periodPaymentRate" -> values.add( + loan.getPaymentRate() == null ? null : new Utils.DoubleFormatter(loan.getPaymentRate().doubleValue()).format()); + case "discount" -> values.add( + loan.getDiscountFee() == null ? "null" : new Utils.DoubleFormatter(loan.getDiscountFee().doubleValue()).format()); + case "discountProposed" -> values.add(loan.getProposedDiscountFee() == null ? "null" + : new Utils.DoubleFormatter(loan.getProposedDiscountFee().doubleValue()).format()); + case "discountApproved" -> values.add(loan.getApprovedDiscountFee() == null ? "null" + : new Utils.DoubleFormatter(loan.getApprovedDiscountFee().doubleValue()).format()); default -> throw new IllegalStateException(String.format("Header name %s cannot be found", field)); } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java index 94fe8c09016..023cb21b152 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanAccountStepDef.java @@ -87,6 +87,7 @@ import org.apache.fineract.client.models.PutWorkingCapitalLoansLoanIdResponse; import org.apache.fineract.client.models.WorkingCapitalLoanCommandTemplateData; import org.apache.fineract.client.models.WorkingCapitalLoanPeriodPaymentRateChangeData; +import org.apache.fineract.test.data.FundId; import org.apache.fineract.test.data.LoanStatus; import org.apache.fineract.test.data.TransactionType; import org.apache.fineract.test.data.codevalue.CodeNames; @@ -186,6 +187,23 @@ public void createWorkingCapitalLoan(final DataTable table) { createWorkingCapitalLoanAccount(data.get(1)); } + @When("Admin creates a working capital loan with fund and the following data:") + public void createWorkingCapitalLoanWithFund(final DataTable table) { + final List loanData = table.asLists().get(1); + final Long clientId = extractClientId(); + final Long loanProductId = resolveLoanProductId(loanData.getFirst()); + final PostWorkingCapitalLoansRequest loansRequest = buildCreateLoanRequest(clientId, loanProductId, loanData) + .fundId(FundId.LENDER_A.value); + testContext().set(TestContextKey.LOAN_CREATE_REQUEST, loansRequest); + + final PostWorkingCapitalLoansResponse response = ok( + () -> fineractClient.workingCapitalLoans().submitWorkingCapitalLoanApplication(loansRequest)); + testContext().set(TestContextKey.LOAN_CREATE_RESPONSE, response); + testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_CREATE_RESPONSE, response); + trackLoanIdIfEnabled(response.getLoanId()); + log.info("Working Capital Loan created with fund {}, Loan ID: {}", FundId.LENDER_A.value, response.getLoanId()); + } + @When("Admin creates a working capital loan using created product with the following data:") public void createWorkingCapitalLoanUsingCreatedProduct(final DataTable table) { submitLoanUsingCreatedProduct(table, null); @@ -264,6 +282,65 @@ public void verifyWorkingCapitalLoanAccountData(final DataTable table) { log.info("Verified working capital loan account data for loan ID: {}", loanId); } + @Then("Working capital loan details has the auto-generated fields present") + public void verifyAutoGeneratedFieldsPresent() { + final Long loanId = getCreatedLoanId(); + final GetWorkingCapitalLoansLoanIdResponse response = retrieveLoanDetails(loanId); + + assertThat(response.getId()).as("id").isNotNull(); + assertThat(response.getAccountNo()).as("accountNo").isNotNull(); + assertThat(response.getExternalId()).as("externalId").isNotNull(); + assertThat(response.getClientId()).as("clientId").isNotNull(); + } + + @Then("Working capital loan details has the following field values:") + public void verifyWorkingCapitalLoanDetailFieldValues(final DataTable table) { + final GetWorkingCapitalLoansLoanIdResponse response = retrieveLoanDetails(getCreatedLoanId()); + + table.asMap().forEach((field, expected) -> { + final String actual = resolveFieldValue(response, field); + if ("present".equals(expected)) { + assertThat(actual).as("WC loan details field %s", field).isNotEqualTo("null"); + } else { + assertThat(actual).as("WC loan details field %s", field).isEqualTo(expected); + } + }); + } + + private String resolveFieldValue(final Object root, final String path) { + Object current = root; + for (final String segment : path.split("\\.", -1)) { + if (current == null) { + return "null"; + } + if (current instanceof List list) { + if ("size".equals(segment)) { + return String.valueOf(list.size()); + } + current = list.get(Integer.parseInt(segment)); + continue; + } + current = invokeGetter(current, segment); + } + return current instanceof BigDecimal amount ? new Utils.DoubleFormatter(amount.doubleValue()).format() : asString(current); + } + + private static Object invokeGetter(final Object owner, final String property) { + final Method getter = Arrays.stream(owner.getClass().getMethods()) + .filter(method -> method.getParameterCount() == 0 && method.getName().equalsIgnoreCase("get" + property)).findFirst() + .orElseThrow( + () -> new IllegalStateException(String.format("No getter '%s' on %s", property, owner.getClass().getSimpleName()))); + try { + return getter.invoke(owner); + } catch (final ReflectiveOperationException e) { + throw new IllegalStateException(e); + } + } + + private static String asString(final Object value) { + return value == null ? "null" : value.toString(); + } + @Then("Creating a working capital loan with LP overridables disabled and with the following data will result an error:") public void creatingWorkingCapitalLoanWithLpOverridablesDisabledWillResultAnError(final DataTable table) { final List> data = table.asLists(); @@ -527,8 +604,8 @@ public void checkCreateWCLoanAccountBreachDataFromWCLP() { GetWorkingCapitalLoansLoanIdResponse loanProductResponse = fineractClient.workingCapitalLoans() .retrieveWorkingCapitalLoanById(loanId); - Assertions.assertNotNull(loanProductResponse.getProduct()); - final Long loanProductId = loanProductResponse.getProduct().getId(); + Assertions.assertNotNull(loanProductResponse.getLoanProductId()); + final Long loanProductId = loanProductResponse.getLoanProductId(); final Long breachIdFromWCLP = getBreachIdFromWCLP(loanProductId); checkCreateWCLoanAccountBreachData(breachIdFromWCLP); @@ -562,7 +639,7 @@ public void checkCreateWCLoanAccountBreachNearBreachDataFromWCLP() { GetWorkingCapitalLoansLoanIdResponse loanProductResponse = fineractClient.workingCapitalLoans() .retrieveWorkingCapitalLoanById(loanId); - final Long loanProductId = loanProductResponse.getProduct().getId(); + final Long loanProductId = loanProductResponse.getLoanProductId(); final Long breachIdFromWCLP = getBreachIdFromWCLP(loanProductId); final Long nearBreachIdFromWCLP = getNearBreachIdFromWCLP(loanProductId); @@ -1973,9 +2050,10 @@ private List fetchValuesOfWorkingCapitalLoan(final List header, final List actualValues = new ArrayList<>(); for (final String headerName : header) { switch (headerName) { - case "product.name" -> actualValues.add(response.getProduct() == null ? null : response.getProduct().getName()); + case "product.name" -> actualValues.add(response.getLoanProductName()); case "submittedOnDate" -> - actualValues.add(response.getSubmittedOnDate() == null ? null : response.getSubmittedOnDate().toString()); + actualValues.add(response.getTimeline() == null || response.getTimeline().getSubmittedOnDate() == null ? null + : response.getTimeline().getSubmittedOnDate().toString()); case "expectedDisbursementDate" -> actualValues.add(response.getDisbursementDetails() == null || response.getDisbursementDetails().isEmpty() ? null : response.getDisbursementDetails().getFirst().getExpectedDisbursementDate().toString()); @@ -1988,14 +2066,14 @@ private List fetchValuesOfWorkingCapitalLoan(final List header, : new Utils.DoubleFormatter(response.getApprovedPrincipal().doubleValue()).format()); case "totalPaymentVolume" -> actualValues.add(response.getTotalPaymentVolume() == null ? null : new Utils.DoubleFormatter(response.getTotalPaymentVolume().doubleValue()).format()); - case "periodPaymentRate" -> actualValues.add(response.getPeriodPaymentRate() == null ? null - : new Utils.DoubleFormatter(response.getPeriodPaymentRate().doubleValue()).format()); - case "discount" -> actualValues.add( - response.getDiscount() == null ? "null" : new Utils.DoubleFormatter(response.getDiscount().doubleValue()).format()); - case "discountProposed" -> actualValues.add(response.getDiscountProposed() == null ? "null" - : new Utils.DoubleFormatter(response.getDiscountProposed().doubleValue()).format()); - case "discountApproved" -> actualValues.add(response.getDiscountApproved() == null ? "null" - : new Utils.DoubleFormatter(response.getDiscountApproved().doubleValue()).format()); + case "periodPaymentRate" -> actualValues.add(response.getPaymentRate() == null ? null + : new Utils.DoubleFormatter(response.getPaymentRate().doubleValue()).format()); + case "discount" -> actualValues.add(response.getDiscountFee() == null ? "null" + : new Utils.DoubleFormatter(response.getDiscountFee().doubleValue()).format()); + case "discountProposed" -> actualValues.add(response.getProposedDiscountFee() == null ? "null" + : new Utils.DoubleFormatter(response.getProposedDiscountFee().doubleValue()).format()); + case "discountApproved" -> actualValues.add(response.getApprovedDiscountFee() == null ? "null" + : new Utils.DoubleFormatter(response.getApprovedDiscountFee().doubleValue()).format()); case "totalPaidPrincipal" -> actualValues.add(response.getBalance() == null || response.getBalance().getPrincipalPaid() == null ? null : new Utils.DoubleFormatter(response.getBalance().getPrincipalPaid().doubleValue()).format()); @@ -3390,8 +3468,8 @@ public void updatePeriodPaymentRateFailed(String periodPaymentRate, String error public void checkWorkingCapitalPeriodPaymentRate(Long loanId, String periodPaymentRate) { final GetWorkingCapitalLoansLoanIdResponse loanDetailsResponse = retrieveLoanDetails(loanId); - assert loanDetailsResponse.getPeriodPaymentRate() != null; - assertThat(loanDetailsResponse.getPeriodPaymentRate().compareTo(new BigDecimal(periodPaymentRate))).isZero(); + assert loanDetailsResponse.getPaymentRate() != null; + assertThat(loanDetailsResponse.getPaymentRate().compareTo(new BigDecimal(periodPaymentRate))).isZero(); } public void checkPeriodPaymentRateChangeHistory(List> data, diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanOriginationStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanOriginationStepDef.java index 3253e8d8669..6dde40e6ebc 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanOriginationStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalLoanOriginationStepDef.java @@ -37,7 +37,6 @@ import lombok.RequiredArgsConstructor; import org.apache.fineract.client.feign.FineractFeignClient; import org.apache.fineract.client.feign.util.CallFailedRuntimeException; -import org.apache.fineract.client.models.GetLoanOriginatorsResponse; import org.apache.fineract.client.models.GetWorkingCapitalLoansLoanIdOriginatorData; import org.apache.fineract.client.models.GetWorkingCapitalLoansLoanIdResponse; import org.apache.fineract.client.models.LoanOriginatorsResponse; @@ -279,7 +278,6 @@ public void detailsHasOriginatorWithAllFields() { final String expectedExternalId = getExternalId(TestContextKey.ORIGINATOR_EXTERNAL_ID); final String expectedOriginatorTypeName = testContext().get(TestContextKey.ORIGINATOR_TYPE_NAME); final String expectedChannelTypeName = testContext().get(TestContextKey.ORIGINATOR_CHANNEL_TYPE_NAME); - final long originatorId = getOriginatorId(TestContextKey.ORIGINATOR_CREATE_RESPONSE); final List originators = retrieveLoanDetails().getOriginators(); assertThat(originators).as("Originators in WC loan details").isNotNull().isNotEmpty(); @@ -288,15 +286,14 @@ public void detailsHasOriginatorWithAllFields() { () -> new AssertionError("Originator with externalId " + expectedExternalId + " not found in WC loan details")); assertThat(originator.getId()).as("Originator id in WC loan details").isNotNull(); + assertThat(originator.getExternalId()).as("Originator externalId in WC loan details").isEqualTo(expectedExternalId); assertThat(originator.getName()).as("Originator name in WC loan details").isNotNull(); assertThat(originator.getStatus()).as("Originator status in WC loan details").isEqualTo("ACTIVE"); - - final GetLoanOriginatorsResponse originatorDetails = ok( - () -> fineractClient.loanOriginators().retrieveOneLoanOriginator(originatorId)); - assertThat(originatorDetails.getOriginatorType()).as("Originator type").isNotNull(); - assertThat(originatorDetails.getOriginatorType().getName()).as("Originator type name").isEqualTo(expectedOriginatorTypeName); - assertThat(originatorDetails.getChannelType()).as("Channel type").isNotNull(); - assertThat(originatorDetails.getChannelType().getName()).as("Channel type name").isEqualTo(expectedChannelTypeName); + assertThat(originator.getOriginatorType()).as("Originator type in WC loan details").isNotNull(); + assertThat(originator.getOriginatorType().getName()).as("Originator type name in WC loan details") + .isEqualTo(expectedOriginatorTypeName); + assertThat(originator.getChannelType()).as("Channel type in WC loan details").isNotNull(); + assertThat(originator.getChannelType().getName()).as("Channel type name in WC loan details").isEqualTo(expectedChannelTypeName); } @Then("Working capital loan details has originator with name {string}") diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanDetails.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanDetails.feature new file mode 100644 index 00000000000..70a84da7329 --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanDetails.feature @@ -0,0 +1,179 @@ +@WorkingCapital +@WorkingCapitalLoanDetailsFeature +Feature: Working Capital Loan Details + + Scenario: Loan details GET returns the general fields aligned with the Term loan API + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP | 01 January 2026 | 01 January 2026 | 100.0 | 100.0 | 1.0 | 0.0 | + Then Working capital loan creation was successful + When Admin successfully approves the working capital loan on "01 January 2026" with "100" amount and expected disbursement date on "01 January 2026" + Then Working capital loan approval was successful + When Admin successfully disburse the Working Capital loan on "01 January 2026" with "100" EUR transaction amount + Then Verify Working Capital loan disbursement was successful + Then Working capital loan details has the following field values: + | clientId | present | + | clientAccountNo | present | + | clientName | present | + | clientOfficeId | 1 | + | loanProductId | present | + | loanProductName | WCLP | + | status.value | Active | + | status.id | 300 | + | status.code | loanStatusType.active | + | status.pendingApproval | false | + | status.waitingForDisbursal | false | + | status.active | true | + | status.closedObligationsMet | false | + | status.closedWrittenOff | false | + | status.closedRescheduled | false | + | status.closed | false | + | status.overpaid | false | + | proposedPrincipal | 100.0 | + | approvedPrincipal | 100.0 | + | principal | 100.0 | + | netDisbursalAmount | 100.0 | + | totalPaymentVolume | 100.0 | + | paymentRate | 1.0 | + | periodPaymentAmount | 0.0 | + | numberOfRepayments | 36000 | + | dailyEir | 0.0 | + | calculatedAnnualEir | 0.0 | + | proposedDiscountFee | 0.0 | + | approvedDiscountFee | null | + | discountFee | null | + | amortizationType.code | EIR | + | amortizationType.value | present | + | npvDayCount | 360 | + | loanProductCounter | 1 | + | currency.code | EUR | + | currency.name | Euro | + | currency.decimalPlaces | 2 | + | currency.inMultiplesOf | 1 | + | currency.displaySymbol | € | + | currency.nameCode | currency.EUR | + | currency.displayLabel | Euro (€) | + | fundId | null | + | fundName | null | + | repaymentEvery | 30 | + | repaymentFrequencyType.code | DAYS | + | repaymentFrequencyType.value | present | + | delinquencyGraceDays | null | + | delinquencyStartType.code | null | + | breachGraceDays | 0 | + | delinquencyBucket | present | + | breach | null | + | nearBreach | null | + | breachStartDate | null | + | delinquencyStartDate | null | + | lastClosedBusinessDate | null | + | balance.principal | 100.0 | + | balance.principalPaid | 0.0 | + | balance.principalOutstanding | 100.0 | + | balance.totalDisbursement | 0.0 | + | balance.totalRepayment | 0.0 | + | balance.totalOutstanding | 100.0 | + | balance.totalExpectedRepayment | 100.0 | + | balance.id | present | + | balance.fee | 0.0 | + | balance.feePaid | 0.0 | + | balance.feeOutstanding | 0.0 | + | balance.penalty | 0.0 | + | balance.penaltyPaid | 0.0 | + | balance.penaltyOutstanding | 0.0 | + | balance.realizedIncomeFromDiscountFee | 0.0 | + | balance.unrealizedIncomeFromDiscountFee | 0.0 | + | balance.overpaymentAmount | 0.0 | + | balance.totalDiscountFee | 0.0 | + | balance.totalDiscountFeeAdjustment | 0.0 | + | summary.principal | 100.0 | + | summary.principalOutstanding | 100.0 | + | summary.totalDisbursement | 0.0 | + | summary.totalOutstanding | 100.0 | + | summary.currency.code | EUR | + | summary.principalPaid | 0.0 | + | summary.fee | 0.0 | + | summary.feePaid | 0.0 | + | summary.feeOutstanding | 0.0 | + | summary.penalty | 0.0 | + | summary.penaltyPaid | 0.0 | + | summary.penaltyOutstanding | 0.0 | + | summary.realizedIncomeFromDiscountFee | 0.0 | + | summary.unrealizedIncomeFromDiscountFee | 0.0 | + | summary.overpayment | 0.0 | + | summary.totalDiscountFee | 0.0 | + | summary.totalDiscountFeeAdjustment | 0.0 | + | summary.totalExpectedRepayment | 100.0 | + | summary.totalRepayment | 0.0 | + | delinquent.pastDueDays | 0 | + | delinquent.delinquentDays | 0 | + | delinquent.delinquentAmount | 0.0 | + | delinquent.delinquentPrincipal | 0.0 | + | delinquent.delinquencyPausePeriods.size | 0 | + | delinquent.delinquentDate | null | + | delinquent.installmentLevelDelinquency.size | 0 | + | timeline.submittedOnDate | 2026-01-01 | + | timeline.submittedByUsername | mifos | + | timeline.submittedByFirstname | App | + | timeline.submittedByLastname | Administrator | + | timeline.approvedOnDate | 2026-01-01 | + | timeline.approvedByUsername | mifos | + | timeline.approvedByFirstname | App | + | timeline.approvedByLastname | Administrator | + | timeline.rejectedOnDate | null | + | timeline.expectedDisbursementDate | 2026-01-01 | + | timeline.actualDisbursementDate | 2026-01-01 | + | timeline.disbursedByUsername | mifos | + | timeline.disbursedByFirstname | App | + | timeline.disbursedByLastname | Administrator | + | timeline.expectedMaturityDate | null | + | timeline.actualMaturityDate | null | + | charges.size | 0 | + | disbursementDetails.size | 1 | + | disbursementDetails.0.id | present | + | disbursementDetails.0.loanId | present | + | disbursementDetails.0.principal | 100.0 | + | disbursementDetails.0.expectedDisbursementDate | 2026-01-01 | + | disbursementDetails.0.actualDisbursementDate | 2026-01-01 | + | paymentAllocation.size | 1 | + | paymentAllocation.0.transactionType | present | + | originators.size | 0 | + | enableInstallmentLevelDelinquency | true | + | fraud | null | + | chargedOff | null | + And Working capital loan details has the auto-generated fields present + + Scenario: Loan details GET returns the fund when the loan is created with one + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with fund and the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP | 01 January 2026 | 01 January 2026 | 100.0 | 100.0 | 1.0 | 0.0 | + Then Working capital loan creation was successful + Then Working capital loan details has the following field values: + | fundId | 1 | + | fundName | Lender A | + + Scenario: Loan details GET returns the charges added to the loan + Given Admin sets the business date to "01 January 2026" + And Admin creates a client with random data and creates-approves-disburses a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPaymentVolume | periodPaymentRate | discount | + | WCLP | 01 January 2026 | 01 January 2026 | 100.0 | 100.0 | 1.0 | 0.0 | + And Admin adds "WORKING_CAPITAL_SPECIFIED_DUE_DATE_FEE" specified due date charge to working capital loan with "12 January 2026" due date and 35.0 transaction amount + Then Working capital loan details has the following field values: + | charges.size | 1 | + | charges.0.id | present | + | charges.0.chargeId | present | + | charges.0.name | Working Capital Loan Fee | + | charges.0.amount | 35.0 | + | charges.0.amountOutstanding | 35.0 | + | charges.0.dueDate | 2026-01-12 | + | charges.0.penalty | false | + | charges.0.paid | false | + | charges.0.loanId | present | + | charges.0.currency.code | EUR | + | charges.0.chargeTimeType.value | Specified due date | + | charges.0.chargeCalculationType.value | Flat | + | charges.0.chargePaymentMode.value | Regular | diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResourceSwagger.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResourceSwagger.java index 78ed4c3d725..e2a141d17d9 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResourceSwagger.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanApiResourceSwagger.java @@ -23,6 +23,8 @@ import java.time.LocalDate; import java.util.Collection; import java.util.List; +import org.apache.fineract.infrastructure.codes.data.CodeValueData; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.StringEnumOptionData; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData; @@ -131,6 +133,14 @@ private GetWorkingCapitalLoansLoanIdTimeline() {} public String approvedByFirstname; @Schema(example = "Administrator") public String approvedByLastname; + @Schema(example = "[2024, 1, 15]") + public LocalDate rejectedOnDate; + @Schema(example = "admin") + public String rejectedByUsername; + @Schema(example = "App") + public String rejectedByFirstname; + @Schema(example = "Administrator") + public String rejectedByLastname; @Schema(example = "[2024, 2, 1]") public LocalDate expectedDisbursementDate; @Schema(example = "[2024, 2, 1]") @@ -170,39 +180,60 @@ private GetWorkingCapitalLoansLoanIdResponse() {} public String accountNo; @Schema(example = "ext-id-001") public String externalId; + @Schema(description = "Client object. Populated only by the loan template endpoint; null in loan details " + + "(loan details exposes clientId/clientAccountNo/clientName/clientOfficeId instead)") public GetWorkingCapitalLoansClient client; @Schema(example = "1") - public Long officeId; + public Long clientId; + @Schema(example = "000000001") + public String clientAccountNo; + @Schema(example = "bharath gowda") + public String clientName; + @Schema(example = "1") + public Long clientOfficeId; @Schema(example = "1") public Long fundId; @Schema(example = "Fund 1") public String fundName; + @Schema(description = "Product object. Populated only by the loan template endpoint; null in loan details " + + "(loan details exposes loanProductId/loanProductName instead)") public WorkingCapitalLoanProductApiResourceSwagger.GetWorkingCapitalLoanProductsResponse product; + @Schema(example = "1") + public Long loanProductId; + @Schema(example = "BNPL") + public String loanProductName; public GetWorkingCapitalLoansLoanIdStatus status; public GetWorkingCapitalLoansLoanIdTimeline timeline; - @Schema(example = "[2024, 1, 15]") - public LocalDate submittedOnDate; - public LocalDate approvedOnDate; - public LocalDate rejectedOnDate; public BigDecimal proposedPrincipal; public BigDecimal approvedPrincipal; + @Schema(example = "10000.00", description = "Active principal (loanProductRelatedDetails.principal)") + public BigDecimal principal; + @Schema(example = "10000.00", description = "Net disbursal amount from the amortization schedule; null if schedule not yet generated") + public BigDecimal netDisbursalAmount; public CurrencyData currency; @Schema(example = "1.0") - public BigDecimal periodPaymentRate; + public BigDecimal paymentRate; @Schema(example = "30") public Integer repaymentEvery; public StringEnumOptionData repaymentFrequencyType; + @Schema(description = "Amortization type: EIR or FLAT") + public StringEnumOptionData amortizationType; + @Schema(example = "360", description = "NPV day count used by the amortization schedule") + public Integer npvDayCount; + @Schema(example = "1", description = "Loan cycle (sequential WC loan counter per client+product)") + public Integer loanProductCounter; @Schema(example = "10500.00") public BigDecimal totalPaymentVolume; - @Schema(example = "0.0", description = "Discount set during loan disbursement") - public BigDecimal discount; - @Schema(example = "0.0", description = "Proposed discount at loan submission time") - public BigDecimal discountProposed; - @Schema(example = "0.0", description = "Approved discount set during loan approval") - public BigDecimal discountApproved; - @Schema(example = "90", description = "Loan term in days (originalPaymentNumber from amortization schedule); null if schedule not yet generated") - public Integer totalNoPayments; + @Schema(example = "0.0", description = "Discount fee set during loan disbursement") + public BigDecimal discountFee; + @Schema(example = "0.0", description = "Proposed discount fee at loan submission time") + public BigDecimal proposedDiscountFee; + @Schema(example = "0.0", description = "Approved discount fee set during loan approval") + public BigDecimal approvedDiscountFee; + @Schema(example = "90", description = "Number of repayments (effectiveTotalTerm from the amortization schedule; for WC this is the " + + "loan term in days); null if schedule not yet generated") + public Integer numberOfRepayments; @Schema(example = "116.67", description = "Daily expected payment amount from the amortization schedule; null if schedule not yet generated") public BigDecimal periodPaymentAmount; @Schema(example = "0.000435", description = "Periodic (daily) effective interest rate computed via RATE(); null if schedule not yet generated") @@ -232,14 +263,83 @@ private GetWorkingCapitalLoansLoanIdResponse() {} * Full list of disbursement details (timeline uses the first). */ public List disbursementDetails; + @Schema(description = "Charges associated with the loan") + public List charges; /** * Running balances (principal outstanding, total payment, etc.). */ public GetBalance balance; + @Schema(description = "Loan summary: principal / fee / penalty totals, income recognition and aggregates") + public GetWorkingCapitalLoanSummary summary; + + @Schema(description = "Working Capital Loan charge") + public static final class GetWorkingCapitalLoanCharge { + + private GetWorkingCapitalLoanCharge() {} + + @Schema(example = "1") + public Long id; + @Schema(example = "1") + public Long chargeId; + @Schema(example = "nsf fees") + public String name; + public EnumOptionData chargeTimeType; + public LocalDate submittedOnDate; + public LocalDate dueDate; + public EnumOptionData chargeCalculationType; + public CurrencyData currency; + @Schema(example = "10") + public BigDecimal amount; + @Schema(example = "10") + public BigDecimal amountPaid; + @Schema(example = "0") + public BigDecimal amountOutstanding; + @Schema(example = "false") + public boolean penalty; + public EnumOptionData chargePaymentMode; + @Schema(example = "true") + public boolean paid; + @Schema(example = "1") + public Long loanId; + } + + @Schema(description = "Working Capital Loan summary") + public static final class GetWorkingCapitalLoanSummary { + + private GetWorkingCapitalLoanSummary() {} + + public CurrencyData currency; + public BigDecimal principal; + public BigDecimal principalPaid; + public BigDecimal principalOutstanding; + public BigDecimal fee; + public BigDecimal feePaid; + public BigDecimal feeOutstanding; + public BigDecimal penalty; + public BigDecimal penaltyPaid; + public BigDecimal penaltyOutstanding; + public BigDecimal realizedIncomeFromDiscountFee; + public BigDecimal unrealizedIncomeFromDiscountFee; + public BigDecimal overpayment; + public BigDecimal totalDisbursement; + public BigDecimal totalDiscountFee; + public BigDecimal totalDiscountFeeAdjustment; + public BigDecimal totalExpectedRepayment; + public BigDecimal totalRepayment; + public BigDecimal totalOutstanding; + } + @Schema(description = "Working Capital Delinquency Collection Data") - public WorkingCapitalCollection collectionData; + public WorkingCapitalCollection delinquent; + @Schema(description = "Installment-level delinquency flag (Term-compatible name). True when the loan has a delinquency " + + "bucket configured (Working Capital tracks delinquency at the period level); false otherwise", example = "true") + public Boolean enableInstallmentLevelDelinquency; @Schema(description = "List of originators associated with this loan") public List originators; + @Schema(description = "Fraud flag. Placeholder: null until the WCP fraud feature is implemented") + public Boolean fraud; + @Schema(description = "Charge-off flag. Placeholder: null until the WCP charge-off feature is implemented") + public Boolean chargedOff; @Schema(description = "Originator data associated with the loan") public static final class GetWorkingCapitalLoansLoanIdOriginatorData { @@ -254,14 +354,10 @@ private GetWorkingCapitalLoansLoanIdOriginatorData() {} public String name; @Schema(example = "ACTIVE") public String status; - @Schema(example = "1") - public Long originatorTypeId; - @Schema(example = "MERCHANT") - public String originatorTypeName; - @Schema(example = "2") - public Long channelTypeId; - @Schema(example = "ONLINE") - public String channelTypeName; + @Schema(description = "Originator type as a code value (id, name, ...)") + public CodeValueData originatorType; + @Schema(description = "Channel type as a code value (id, name, ...)") + public CodeValueData channelType; } } @@ -315,11 +411,16 @@ public static final class GetDisbursementDetail { private GetDisbursementDetail() {} + @Schema(example = "1") public Long id; + @Schema(example = "1") + public Long loanId; public LocalDate expectedDisbursementDate; - public BigDecimal expectedAmount; + @Schema(example = "10000.00", description = "Expected (planned) disbursement principal") + public BigDecimal principal; public LocalDate expectedMaturityDate; public LocalDate actualDisbursementDate; + @Schema(example = "10000.00", description = "Actually disbursed amount; null until disbursed") public BigDecimal actualAmount; public String disbursedByUsername; public String disbursedByFirstname; @@ -638,7 +739,9 @@ public static final class WorkingCapitalCollection { private WorkingCapitalCollection() {} - @Schema(description = "Working capital loan delinquency collection summary", example = "true") + @Schema(description = "Days the oldest unmet minimum-payment period is past due (measured from its toDate); 0 when not past due", example = "0") + public Long pastDueDays; + @Schema(description = "Number of days the loan has been delinquent, from the oldest active delinquency tag", example = "0") public Long delinquentDays; @Schema(description = "Date when the loan became delinquent", example = "[2024, 1, 15]") public LocalDate delinquentDate; @@ -646,14 +749,10 @@ private WorkingCapitalCollection() {} public BigDecimal delinquentAmount; @Schema(description = "Pause periods during which delinquency is not counted") public Collection delinquencyPausePeriods; - @Schema(description = "Delinquency amounts grouped by age range") - public Collection rangeLevelDelinquency; + @Schema(description = "Delinquency amounts grouped by age range (installment-level delinquency; Term-compatible name)") + public Collection installmentLevelDelinquency; @Schema(description = "Delinquent principal amount", example = "1000.00") public BigDecimal delinquentPrincipal; - @Schema(description = "Delinquent fee amount", example = "150.00") - public BigDecimal delinquentFee; - @Schema(description = "Delinquent penalty amount", example = "84.56") - public BigDecimal delinquentPenalty; @Schema(description = "Delinquency amount for a specific age range") public static final class WorkingCapitalCollectionRangeScheduleDelinquency { diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanChargeData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanChargeData.java index df9491169cf..d137adde8ff 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanChargeData.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanChargeData.java @@ -26,7 +26,6 @@ import lombok.Builder; import lombok.Getter; import org.apache.fineract.infrastructure.core.data.EnumOptionData; -import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.charge.data.ChargeData; @@ -69,14 +68,10 @@ public final class WorkingCapitalLoanChargeData { private final Long loanId; - private final ExternalId externalId; - - private final ExternalId externalLoanId; - public WorkingCapitalLoanChargeData(Long id, Long chargeId, String name, ChargeTimeType chargeTimeType, LocalDate submittedOnDate, LocalDate dueDate, ChargeCalculationType chargeCalculationType, String cCode, String cName, Integer cDecimalPlaces, Integer cInMultiplesOf, String cDisplaySymbol, String cNameCode, BigDecimal amount, BigDecimal amountPaid, boolean penalty, - ChargePaymentMode chargePaymentMode, boolean paid, Long loanId, ExternalId externalId, ExternalId externalLoanId) { + ChargePaymentMode chargePaymentMode, boolean paid, Long loanId) { this.id = id; this.chargeId = chargeId; this.name = name; @@ -93,13 +88,10 @@ public WorkingCapitalLoanChargeData(Long id, Long chargeId, String name, ChargeT this.chargePaymentMode = ChargeEnumerations.chargePaymentMode(chargePaymentMode); this.paid = paid; this.loanId = loanId; - this.externalId = externalId; - this.externalLoanId = externalLoanId; } public static WorkingCapitalLoanChargeData template(final List chargeOptions) { - return WorkingCapitalLoanChargeData.builder().chargeOptions(chargeOptions).externalLoanId(ExternalId.empty()) - .externalLoanId(ExternalId.empty()).build(); + return WorkingCapitalLoanChargeData.builder().chargeOptions(chargeOptions).build(); } } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanCollectionData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanCollectionData.java index f116f3d8550..3d5bfff3c8d 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanCollectionData.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanCollectionData.java @@ -31,19 +31,17 @@ @AllArgsConstructor public class WorkingCapitalLoanCollectionData { + private Long pastDueDays; private Long delinquentDays; private LocalDate delinquentDate; private BigDecimal delinquentAmount; public List delinquencyPausePeriods; - public List rangeLevelDelinquency; + public List installmentLevelDelinquency; private BigDecimal delinquentPrincipal; - private BigDecimal delinquentFee; - private BigDecimal delinquentPenalty; public static WorkingCapitalLoanCollectionData initializeEmptyData() { - return new WorkingCapitalLoanCollectionData(0L, null, BigDecimal.ZERO, null, new ArrayList<>(), BigDecimal.ZERO, BigDecimal.ZERO, - BigDecimal.ZERO); + return new WorkingCapitalLoanCollectionData(0L, 0L, null, BigDecimal.ZERO, new ArrayList<>(), new ArrayList<>(), BigDecimal.ZERO); } } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanData.java index 70ae8a3d012..5952f7b25a6 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanData.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanData.java @@ -54,26 +54,33 @@ public class WorkingCapitalLoanData implements Serializable { private String accountNo; private ExternalId externalId; private ClientData client; - private Long officeId; - private String officeName; + private Long clientId; + private String clientAccountNo; + private String clientName; + private Long clientOfficeId; private Long fundId; private String fundName; private WorkingCapitalLoanProductData product; + private Long loanProductId; + private String loanProductName; private LoanStatusEnumData status; - private LocalDate submittedOnDate; - private LocalDate approvedOnDate; - private LocalDate rejectedOnDate; private BigDecimal proposedPrincipal; private BigDecimal approvedPrincipal; + private BigDecimal principal; + private BigDecimal netDisbursalAmount; + private StringEnumOptionData amortizationType; + private Integer npvDayCount; + private Integer loanProductCounter; + private List charges; private CurrencyData currency; - private BigDecimal periodPaymentRate; + private BigDecimal paymentRate; private Integer repaymentEvery; private StringEnumOptionData repaymentFrequencyType; - private BigDecimal discount; - private BigDecimal discountProposed; - private BigDecimal discountApproved; - private Integer totalNoPayments; + private BigDecimal discountFee; + private BigDecimal proposedDiscountFee; + private BigDecimal approvedDiscountFee; + private Integer numberOfRepayments; private BigDecimal periodPaymentAmount; private BigDecimal dailyEir; private BigDecimal calculatedAnnualEir; @@ -91,8 +98,10 @@ public class WorkingCapitalLoanData implements Serializable { private BigDecimal totalPaymentVolume; private LocalDate delinquencyStartDate; private LocalDate breachStartDate; - - private WorkingCapitalLoanCollectionData collectionData; + private WorkingCapitalLoanCollectionData delinquent; + private Boolean enableInstallmentLevelDelinquency; private WorkingCapitalLoanSummaryData summary; private List originators; + private Boolean fraud; + private Boolean chargedOff; } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanDisbursementDetailData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanDisbursementDetailData.java index 191ed2e0cf8..7d626c45ca3 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanDisbursementDetailData.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanDisbursementDetailData.java @@ -37,8 +37,9 @@ public class WorkingCapitalLoanDisbursementDetailData { private Long id; + private Long loanId; private LocalDate expectedDisbursementDate; - private BigDecimal expectedAmount; + private BigDecimal principal; private LocalDate expectedMaturityDate; private LocalDate actualDisbursementDate; private BigDecimal actualAmount; diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanCharge.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanCharge.java index cc3a2cf20d7..4ea58d1d6ae 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanCharge.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/WorkingCapitalLoanCharge.java @@ -105,7 +105,7 @@ public WorkingCapitalLoanChargeData toData() { .currency(getCharge().toData().getCurrency()).amount(amount).amountPaid(amountPaid) .amountOutstanding(getAmountOutstanding()).chargeTimeType(chargeTimeTypeData).submittedOnDate(submittedOnDate) .dueDate(dueDate).chargeCalculationType(chargeCalculationTypeData).penalty(penaltyCharge) - .chargePaymentMode(chargePaymentModeData).paid(paid).loanId(loan.getId()).externalId(externalId).build(); + .chargePaymentMode(chargePaymentModeData).paid(paid).loanId(loan.getId()).build(); } public BigDecimal getAmountOutstanding() { diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanDisbursementDetailMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanDisbursementDetailMapper.java index f6fed1c0f6b..9cbff13aae6 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanDisbursementDetailMapper.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanDisbursementDetailMapper.java @@ -28,6 +28,8 @@ @Mapper(config = MapstructMapperConfig.class) public interface WorkingCapitalLoanDisbursementDetailMapper { + @Mapping(target = "loanId", source = "wcLoan.id") + @Mapping(target = "principal", source = "expectedAmount") @Mapping(target = "disbursedByUsername", source = "disbursedBy.username") @Mapping(target = "disbursedByFirstname", source = "disbursedBy.firstname") @Mapping(target = "disbursedByLastname", source = "disbursedBy.lastname") diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanMapper.java index 1130babaad8..f2667a03e3c 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanMapper.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanMapper.java @@ -23,8 +23,6 @@ import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; import org.apache.fineract.infrastructure.core.data.StringEnumOptionData; import org.apache.fineract.organisation.monetary.data.CurrencyData; -import org.apache.fineract.portfolio.client.data.ClientData; -import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyBucketMapper; import org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData; import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData; @@ -49,20 +47,24 @@ public interface WorkingCapitalLoanMapper { @Mapping(target = "accountNo", source = "accountNumber") - @Mapping(target = "client", source = "client", qualifiedByName = "clientToData") - @Mapping(target = "officeId", source = "client.office.id") - @Mapping(target = "officeName", source = "client.office.name") + @Mapping(target = "client", ignore = true) + @Mapping(target = "clientId", source = "client.id") + @Mapping(target = "clientAccountNo", source = "client.accountNumber") + @Mapping(target = "clientName", source = "client.displayName") + @Mapping(target = "clientOfficeId", source = "client.office.id") @Mapping(target = "fundId", source = "fund.id") @Mapping(target = "fundName", source = "fund.name") - @Mapping(target = "product", source = "loanProduct") + @Mapping(target = "product", ignore = true) + @Mapping(target = "loanProductId", source = "loanProduct.id") + @Mapping(target = "loanProductName", source = "loanProduct.name") @Mapping(target = "status", source = "loanStatus", qualifiedByName = "loanStatusData") @Mapping(target = "currency", source = "loanProductRelatedDetails", qualifiedByName = "monetaryCurrencyToCurrencyData") - @Mapping(target = "periodPaymentRate", source = "loanProductRelatedDetails.periodPaymentRate") + @Mapping(target = "paymentRate", source = "loanProductRelatedDetails.periodPaymentRate") @Mapping(target = "repaymentEvery", source = "loanProductRelatedDetails.repaymentEvery") @Mapping(target = "repaymentFrequencyType", source = "loanProductRelatedDetails", qualifiedByName = "repaymentFrequencyTypeData") - @Mapping(target = "discount", source = "loanProductRelatedDetails.discount") - @Mapping(target = "discountProposed", source = "loanProductRelatedDetails.discountProposed") - @Mapping(target = "discountApproved", source = "loanProductRelatedDetails.discountApproved") + @Mapping(target = "discountFee", source = "loanProductRelatedDetails.discount") + @Mapping(target = "proposedDiscountFee", source = "loanProductRelatedDetails.discountProposed") + @Mapping(target = "approvedDiscountFee", source = "loanProductRelatedDetails.discountApproved") @Mapping(target = "breach", source = "loanProductRelatedDetails.breach") @Mapping(target = "nearBreach", source = "loanProductRelatedDetails.nearBreach") @Mapping(target = "delinquencyBucket", source = "loanProductRelatedDetails.delinquencyBucket") @@ -75,25 +77,27 @@ public interface WorkingCapitalLoanMapper { @Mapping(target = "breachGraceDays", source = "loanProductRelatedDetails.breachGraceDays") @Mapping(target = "breachStartDate", ignore = true) @Mapping(target = "delinquencyStartDate", ignore = true) - @Mapping(target = "collectionData", ignore = true) - @Mapping(target = "totalNoPayments", ignore = true) + @Mapping(target = "delinquent", ignore = true) + @Mapping(target = "numberOfRepayments", ignore = true) @Mapping(target = "periodPaymentAmount", ignore = true) @Mapping(target = "dailyEir", ignore = true) @Mapping(target = "calculatedAnnualEir", ignore = true) @Mapping(target = "summary", source = ".", qualifiedByName = "toSummaryData") @Mapping(target = "totalPaymentVolume", source = "totalPaymentVolume") + @Mapping(target = "principal", source = "loanProductRelatedDetails.principal") + @Mapping(target = "amortizationType", source = "loanProductRelatedDetails", qualifiedByName = "amortizationTypeData") + @Mapping(target = "npvDayCount", source = "loanProductRelatedDetails.npvDayCount") + @Mapping(target = "loanProductCounter", source = "loanProductCounter") + @Mapping(target = "enableInstallmentLevelDelinquency", source = "loanProductRelatedDetails", qualifiedByName = "installmentLevelDelinquencyEnabled") + @Mapping(target = "netDisbursalAmount", ignore = true) + @Mapping(target = "charges", ignore = true) @Mapping(target = "originators", ignore = true) + @Mapping(target = "fraud", ignore = true) + @Mapping(target = "chargedOff", ignore = true) WorkingCapitalLoanData toData(WorkingCapitalLoan loan); List toDataList(List loans); - @Named("clientToData") - default ClientData clientToData(final Client client) { - ClientData clientData = ClientData.instance(client.getId(), client.getDisplayName()); - clientData.setAccountNo(client.getAccountNumber()); - return clientData; - } - @Named("loanStatusData") default LoanStatusEnumData loanStatusData(final LoanStatus loanStatus) { return LoanEnumerations.status(loanStatus); @@ -116,6 +120,17 @@ default StringEnumOptionData delinquencyStartTypeData(final WorkingCapitalLoanPr : null; } + @Named("amortizationTypeData") + default StringEnumOptionData amortizationTypeData(final WorkingCapitalLoanProductRelatedDetails detail) { + return (detail != null && detail.getAmortizationType() != null) ? detail.getAmortizationType().getValueAsStringEnumOptionData() + : null; + } + + @Named("installmentLevelDelinquencyEnabled") + default Boolean installmentLevelDelinquencyEnabled(final WorkingCapitalLoanProductRelatedDetails detail) { + return detail != null && detail.getDelinquencyBucket() != null; + } + @Named("timelineData") default LoanApplicationTimelineData timelineData(final WorkingCapitalLoan loan) { final LoanApplicationTimelineData timelineData = new LoanApplicationTimelineData(); diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanChargeRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanChargeRepository.java index 958dfd507f5..4fc11dd02bb 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanChargeRepository.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/WorkingCapitalLoanChargeRepository.java @@ -39,12 +39,12 @@ public interface WorkingCapitalLoanChargeRepository @Query("select new org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanChargeData(" + "lc.id, c.id, c.name, lc.chargeTimeType, lc.submittedOnDate, lc.dueDate, lc.chargeCalculationType, oc.code, oc.name, oc.decimalPlaces, oc.inMultiplesOf, oc.displaySymbol," - + " oc.nameCode, lc.amount, lc.amountPaid, lc.penaltyCharge, lc.chargePaymentMode, lc.paid, l.id, lc.externalId, l.externalId) from WorkingCapitalLoanCharge lc join fetch lc.charge c join OrganisationCurrency oc on c.currencyCode = oc.code join fetch lc.loan l where l.id = :loanId and lc.id = :id") + + " oc.nameCode, lc.amount, lc.amountPaid, lc.penaltyCharge, lc.chargePaymentMode, lc.paid, l.id) from WorkingCapitalLoanCharge lc join fetch lc.charge c join OrganisationCurrency oc on c.currencyCode = oc.code join fetch lc.loan l where l.id = :loanId and lc.id = :id") WorkingCapitalLoanChargeData retrieveLoanChargeDetails(@Param("id") Long id, @Param("loanId") Long loanId); @Query("select new org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanChargeData(" + "lc.id, c.id, c.name, lc.chargeTimeType, lc.submittedOnDate, lc.dueDate, lc.chargeCalculationType, oc.code, oc.name, oc.decimalPlaces, oc.inMultiplesOf, oc.displaySymbol," - + " oc.nameCode, lc.amount, lc.amountPaid, lc.penaltyCharge, lc.chargePaymentMode, lc.paid, l.id, lc.externalId, l.externalId) from WorkingCapitalLoanCharge lc join fetch lc.charge c join OrganisationCurrency oc on c.currencyCode = oc.code join fetch lc.loan l where l.id = :loanId and lc.active = true order by lc.chargeTimeType asc, lc.dueDate asc, lc.penaltyCharge asc") + + " oc.nameCode, lc.amount, lc.amountPaid, lc.penaltyCharge, lc.chargePaymentMode, lc.paid, l.id) from WorkingCapitalLoanCharge lc join fetch lc.charge c join OrganisationCurrency oc on c.currencyCode = oc.code join fetch lc.loan l where l.id = :loanId and lc.active = true order by lc.chargeTimeType asc, lc.dueDate asc, lc.penaltyCharge asc") List retrieveLoanCharges(@Param("loanId") Long loanId); } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanApplicationReadPlatformServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanApplicationReadPlatformServiceImpl.java index b39eaa923b1..48f56703583 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanApplicationReadPlatformServiceImpl.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanApplicationReadPlatformServiceImpl.java @@ -36,6 +36,7 @@ import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.accountdetails.data.WorkingCapitalLoanAccountSummaryData; import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; @@ -61,6 +62,7 @@ import org.apache.fineract.portfolio.workingcapitalloanproduct.data.WorkingCapitalLoanProductData; import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanDelinquencyStartType; import org.apache.fineract.portfolio.workingcapitalloanproduct.service.WorkingCapitalLoanProductReadPlatformService; +import org.apache.fineract.useradministration.domain.AppUserRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -86,6 +88,9 @@ public class WorkingCapitalLoanApplicationReadPlatformServiceImpl implements Wor private final WorkingCapitalLoanBreachScheduleRepository breachScheduleRepository; private final WorkingCapitalLoanDelinquencyRangeScheduleRepository delinquencyRangeScheduleRepository; private final Optional originatorReadService; + private final WorkingCapitalLoanChargeReadPlatformService chargeReadPlatformService; + private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; + private final AppUserRepository appUserRepository; @Override public WorkingCapitalLoanTemplateData retrieveTemplate(final Long productId, final Long clientId) { @@ -109,10 +114,10 @@ public WorkingCapitalLoanTemplateData retrieveTemplate(final Long productId, fin .fundId(product.getFundId()) // .fundName(product.getFundName()) // .currency(product.getCurrency()) // - .periodPaymentRate(product.getPeriodPaymentRate()) // + .paymentRate(product.getPeriodPaymentRate()) // .repaymentEvery(product.getRepaymentEvery()) // .repaymentFrequencyType(product.getRepaymentFrequencyType()) // - .discount(product.getDiscount()) // + .discountFee(product.getDiscount()) // .paymentAllocation(product.getPaymentAllocation()) // .breach(product.getBreach()) // .nearBreach(product.getNearBreach()) // @@ -175,7 +180,10 @@ public WorkingCapitalLoanData retrieveOne(final Long loanId) { WorkingCapitalLoanData data = this.mapper.toData(loan); WorkingCapitalLoanCollectionData collectionData = workingCapitalLoanDelinquencyReadPlatformService.getCollectionData(loanId, ThreadLocalContextUtil.getBusinessDate()); - data.setCollectionData(collectionData); + data.setDelinquent(collectionData); + data.setCharges(chargeReadPlatformService.retrieveLoanCharges(loanId)); + enrichWithFullCurrency(data); + enrichWithSubmittedBy(loan, data); enrichWithRateAndTerm(loan, data); enrichWithStartDates(loan, data); enrichWithOriginators(loanId, data); @@ -187,13 +195,35 @@ public WorkingCapitalLoanData retrieveOne(final ExternalId externalId) { return retrieveOne(repository.findIdByExternalId(externalId)); } + private void enrichWithFullCurrency(final WorkingCapitalLoanData data) { + final CurrencyData currency = data.getCurrency(); + if (currency == null || currency.getCode() == null) { + return; + } + final CurrencyData appCurrency = applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency.getCode()).toData(); + data.setCurrency(new CurrencyData(currency.getCode(), appCurrency.getName(), currency.getDecimalPlaces(), + currency.getInMultiplesOf(), appCurrency.getDisplaySymbol(), appCurrency.getNameCode())); + } + + private void enrichWithSubmittedBy(final WorkingCapitalLoan loan, final WorkingCapitalLoanData data) { + if (data.getTimeline() == null) { + return; + } + loan.getCreatedBy().flatMap(appUserRepository::findById).ifPresent(user -> { + data.getTimeline().setSubmittedByUsername(user.getUsername()); + data.getTimeline().setSubmittedByFirstname(user.getFirstname()); + data.getTimeline().setSubmittedByLastname(user.getLastname()); + }); + } + private void enrichWithRateAndTerm(final WorkingCapitalLoan loan, final WorkingCapitalLoanData data) { final MathContext mc = MoneyHelper.getMathContext(); final CurrencyData currency = WorkingCapitalLoanCurrencyResolver.resolveCurrency(loan); scheduleRepositoryWrapper.readModel(loan.getId(), mc, currency).ifPresent(model -> { final BigDecimal dailyEir = model.effectiveInterestRate(); - data.setTotalNoPayments(model.effectiveTotalTerm()); + data.setNumberOfRepayments(model.effectiveTotalTerm()); data.setPeriodPaymentAmount(model.expectedPaymentAmount() != null ? model.expectedPaymentAmount().getAmount() : null); + data.setNetDisbursalAmount(model.netDisbursementAmount() != null ? model.netDisbursementAmount().getAmount() : null); data.setDailyEir(dailyEir); if (dailyEir != null) { data.setCalculatedAnnualEir(BigDecimal.ONE.add(dailyEir, mc).pow(365, mc).subtract(BigDecimal.ONE, mc)); diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanDelinquencyReadPlatformServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanDelinquencyReadPlatformServiceImpl.java index 36c956442d1..b54f4e66280 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanDelinquencyReadPlatformServiceImpl.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanDelinquencyReadPlatformServiceImpl.java @@ -30,6 +30,7 @@ import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanCollectionData; import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanDelinquencyTagHistoryData; import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanRangeScheduleDelinquencyData; +import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanDelinquencyRangeSchedule; import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanDelinquencyRangeScheduleTagHistory; import org.apache.fineract.portfolio.workingcapitalloan.mapper.WorkingCapitalLoanDelinquencyRangeScheduleTagHistoryMapper; import org.apache.fineract.portfolio.workingcapitalloan.repository.WorkingCapitalLoanDelinquencyRangeScheduleRepository; @@ -68,7 +69,10 @@ public WorkingCapitalLoanCollectionData getCollectionData(Long loanId, LocalDate template.setDelinquentPrincipal(delinquentAmount); } - template.setRangeLevelDelinquency(list); + delinquencyRangeScheduleRepository.findTopByLoanIdAndMinPaymentCriteriaMetFalseOrderByFromDateAsc(loanId) + .map(WorkingCapitalLoanDelinquencyRangeSchedule::getDelinquentDays).ifPresent(template::setPastDueDays); + + template.setInstallmentLevelDelinquency(list); return template; } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationCRUDTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationCRUDTest.java index 49a6c9233f9..c9c139b8308 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationCRUDTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApplicationCRUDTest.java @@ -135,7 +135,7 @@ public void testSubmitWithoutOverridableParamsUsesProductDefaults() { assertEquals(productRepaymentEvery.intValue(), data.getRepaymentEvery(), "repaymentEvery should come from product"); assertNotNull(data.getRepaymentFrequencyType()); assertEquals(productRepaymentFrequencyType, data.getRepaymentFrequencyType().getValue()); - assertNull(data.getDiscountProposed()); + assertNull(data.getProposedDiscountFee()); assertNotNull(data.getDelinquencyBucket(), "delinquencyBucket should come from product"); assertEquals(delinquencyBucketId.longValue(), data.getDelinquencyBucket().getId(), "delinquencyBucket.id should come from product"); @@ -485,9 +485,9 @@ public void testRetrieveTemplateWithProductId() { assertEquals(productName, loanData.getProduct().getName()); } if (loanData != null) { - if (loanData.getPeriodPaymentRate() != null) { - assertEquals(0, WorkingCapitalLoanProductTestBuilder.DEFAULT_PERIOD_PAYMENT_RATE_PERCENT - .compareTo(loanData.getPeriodPaymentRate())); + if (loanData.getPaymentRate() != null) { + assertEquals(0, + WorkingCapitalLoanProductTestBuilder.DEFAULT_PERIOD_PAYMENT_RATE_PERCENT.compareTo(loanData.getPaymentRate())); } if (loanData.getRepaymentEvery() != null) { assertEquals(30, loanData.getRepaymentEvery()); @@ -823,8 +823,8 @@ public void testWorkingCapitalDiscountAttributes() { .buildSubmitRequest()); GetWorkingCapitalLoansLoanIdResponse loanData = applicationHelper.retrieveLoan(loanId); - assertEquals(0, discountProposed.compareTo(loanData.getDiscountProposed())); - final LocalDate operationDate = loanData.getSubmittedOnDate(); + assertEquals(0, discountProposed.compareTo(loanData.getProposedDiscountFee())); + final LocalDate operationDate = loanData.getTimeline().getSubmittedOnDate(); discountProposed = BigDecimal.valueOf(99); final var modifyJson = new WorkingCapitalLoanApplicationTestBuilder().withDiscount(discountProposed) // @@ -833,55 +833,55 @@ public void testWorkingCapitalDiscountAttributes() { final Long modifiedId = applicationHelper.modifyById(loanId, modifyJson); assertEquals(loanId, modifiedId); loanData = applicationHelper.retrieveLoan(loanId); - assertEquals(0, discountProposed.compareTo(loanData.getDiscountProposed())); + assertEquals(0, discountProposed.compareTo(loanData.getProposedDiscountFee())); // Approve the WC Loan with specific discount BigDecimal discountApproved = BigDecimal.valueOf(97); applicationHelper.approveById(loanId, WorkingCapitalLoanApplicationTestBuilder.buildApproveRequest(operationDate, null, discountApproved)); loanData = applicationHelper.retrieveLoan(loanId); - assertEquals(0, discountProposed.compareTo(loanData.getDiscountProposed())); - assertEquals(0, discountApproved.compareTo(loanData.getDiscountApproved())); + assertEquals(0, discountProposed.compareTo(loanData.getProposedDiscountFee())); + assertEquals(0, discountApproved.compareTo(loanData.getApprovedDiscountFee())); // Undo WC Loan Approval applicationHelper.undoApprovalById(loanId, WorkingCapitalLoanApplicationTestBuilder.buildUndoApproveRequest()); loanData = applicationHelper.retrieveLoan(loanId); - assertEquals(0, discountProposed.compareTo(loanData.getDiscountProposed())); + assertEquals(0, discountProposed.compareTo(loanData.getProposedDiscountFee())); // Null as reset of Approval amount - assertNull(loanData.getDiscountApproved()); + assertNull(loanData.getApprovedDiscountFee()); // ReApprove the WC Loan with specific discount discountApproved = BigDecimal.valueOf(95); applicationHelper.approveById(loanId, WorkingCapitalLoanApplicationTestBuilder.buildApproveRequest(operationDate, null, discountApproved)); loanData = applicationHelper.retrieveLoan(loanId); - assertEquals(0, discountProposed.compareTo(loanData.getDiscountProposed())); - assertEquals(0, discountApproved.compareTo(loanData.getDiscountApproved())); + assertEquals(0, discountProposed.compareTo(loanData.getProposedDiscountFee())); + assertEquals(0, discountApproved.compareTo(loanData.getApprovedDiscountFee())); // Disburse the WC Loan without specific discount then It will use discountApproved applicationHelper.disburseById(loanId, WorkingCapitalLoanDisbursementTestBuilder.buildDisburseRequest(operationDate, BigDecimal.valueOf(5000))); loanData = applicationHelper.retrieveLoan(loanId); - assertEquals(0, discountProposed.compareTo(loanData.getDiscountProposed())); - assertEquals(0, discountApproved.compareTo(loanData.getDiscountApproved())); - assertNull(loanData.getDiscount()); + assertEquals(0, discountProposed.compareTo(loanData.getProposedDiscountFee())); + assertEquals(0, discountApproved.compareTo(loanData.getApprovedDiscountFee())); + assertNull(loanData.getDiscountFee()); // Undo Disburse the WC Loan applicationHelper.undoDisbursalById(loanId, WorkingCapitalLoanDisbursementTestBuilder.buildUndoDisburseRequest("Undo disbursal note")); loanData = applicationHelper.retrieveLoan(loanId); - assertEquals(0, discountProposed.compareTo(loanData.getDiscountProposed())); - assertEquals(0, discountApproved.compareTo(loanData.getDiscountApproved())); - assertNull(loanData.getDiscount()); + assertEquals(0, discountProposed.compareTo(loanData.getProposedDiscountFee())); + assertEquals(0, discountApproved.compareTo(loanData.getApprovedDiscountFee())); + assertNull(loanData.getDiscountFee()); // ReDisburse the WC Loan with specific discount BigDecimal discountDisbursement = BigDecimal.valueOf(80); applicationHelper.disburseById(loanId, WorkingCapitalLoanDisbursementTestBuilder.buildDisburseRequest(operationDate, BigDecimal.valueOf(5000), discountDisbursement, null, null, null, null, null, null, null)); loanData = applicationHelper.retrieveLoan(loanId); - assertEquals(0, discountProposed.compareTo(loanData.getDiscountProposed())); - assertEquals(0, discountApproved.compareTo(loanData.getDiscountApproved())); - assertEquals(0, discountDisbursement.compareTo(loanData.getDiscount())); + assertEquals(0, discountProposed.compareTo(loanData.getProposedDiscountFee())); + assertEquals(0, discountApproved.compareTo(loanData.getApprovedDiscountFee())); + assertEquals(0, discountDisbursement.compareTo(loanData.getDiscountFee())); // Undo Disbursement for delete it applicationHelper.undoDisbursalById(loanId, @@ -896,10 +896,10 @@ private static void assertAllLoanFieldsInResponse(final GetWorkingCapitalLoansLo final Integer repaymentEvery, final String repaymentFrequencyType, final Integer delinquencyGraceDays, final String delinquencyStartType) { assertEquals(loanId, data.getId()); - assertNotNull(data.getClient()); - assertEquals(clientId, data.getClient().getId()); - assertNotNull(data.getProduct()); - assertEquals(productId, data.getProduct().getId()); + assertNotNull(data.getClientId()); + assertEquals(clientId, data.getClientId()); + assertNotNull(data.getLoanProductId()); + assertEquals(productId, data.getLoanProductId()); assertEquals(accountNo, data.getAccountNo()); assertEquals(externalId, data.getExternalId()); if (fundId != null) { @@ -908,9 +908,8 @@ private static void assertAllLoanFieldsInResponse(final GetWorkingCapitalLoansLo assertNotNull(data.getBalance()); assertEqualBigDecimal(principal, data.getProposedPrincipal()); assertEqualBigDecimal(totalPaymentVolume, data.getTotalPaymentVolume()); - assertEqualBigDecimal(periodPaymentRate, data.getPeriodPaymentRate()); - assertEqualBigDecimal(discountProposed, data.getDiscountProposed()); - assertEquals(submittedOnDate, data.getSubmittedOnDate()); + assertEqualBigDecimal(periodPaymentRate, data.getPaymentRate()); + assertEqualBigDecimal(discountProposed, data.getProposedDiscountFee()); assertNotNull(data.getDisbursementDetails()); assertFalse(data.getDisbursementDetails().isEmpty(), "disbursementDetails should not be empty"); assertEquals(expectedDisbursementDate, data.getDisbursementDetails().getFirst().getExpectedDisbursementDate()); @@ -926,10 +925,10 @@ private static void assertAllLoanFieldsInResponse(final GetWorkingCapitalLoansLo assertEquals(repaymentFrequencyType, data.getRepaymentFrequencyType().getCode()); assertNotNull(data.getStatus()); assertEquals("loanStatusType.submitted.and.pending.approval", data.getStatus().getCode()); - assertNotNull(data.getProduct().getName()); - assertFalse(data.getProduct().getName().isBlank()); - assertNotNull(data.getClient().getDisplayName()); - assertFalse(data.getClient().getDisplayName().isBlank()); + assertNotNull(data.getLoanProductName()); + assertFalse(data.getLoanProductName().isBlank()); + assertNotNull(data.getClientName()); + assertFalse(data.getClientName().isBlank()); if (data.getPaymentAllocation() != null) { assertFalse(data.getPaymentAllocation().isEmpty()); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApprovalRejectionTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApprovalRejectionTest.java index 20c28d6cfa1..056b24607d3 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApprovalRejectionTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanApprovalRejectionTest.java @@ -58,7 +58,7 @@ public void testApproveWorkingCapitalLoan() { final GetWorkingCapitalLoansLoanIdResponse data = retrieveLoan(loanId); assert data.getStatus() != null; assertEquals("loanStatusType.approved", data.getStatus().getCode()); - assertEquals(approvedOnDate, data.getApprovedOnDate()); + assertEquals(approvedOnDate, data.getTimeline().getApprovedOnDate()); // approvedPrincipal should default to proposedPrincipal assertNotNull(data.getApprovedPrincipal()); } @@ -91,9 +91,9 @@ public void testApproveWithPrincipalAndDiscountOverride() { assert data.getStatus() != null; assertEquals("loanStatusType.approved", data.getStatus().getCode()); assertEqualBigDecimal(approvedAmount, data.getApprovedPrincipal()); - assertEqualBigDecimal(BigDecimal.valueOf(100), data.getDiscountProposed()); - assertEqualBigDecimal(discountAmount, data.getDiscountApproved()); - assertNull(data.getDiscount()); + assertEqualBigDecimal(BigDecimal.valueOf(100), data.getProposedDiscountFee()); + assertEqualBigDecimal(discountAmount, data.getApprovedDiscountFee()); + assertNull(data.getDiscountFee()); } @Test @@ -108,7 +108,7 @@ public void testRejectWorkingCapitalLoan() { final GetWorkingCapitalLoansLoanIdResponse data = retrieveLoan(loanId); assert data.getStatus() != null; assertEquals("loanStatusType.rejected", data.getStatus().getCode()); - assertEquals(rejectedOnDate, data.getRejectedOnDate()); + assertEquals(rejectedOnDate, data.getTimeline().getRejectedOnDate()); } // ===== AC: User should be able to undo the approval; moves back to created state ===== @@ -152,9 +152,9 @@ public void testUndoApprovalResetsToCreatedState() { final GetWorkingCapitalLoansLoanIdResponse approvedData = retrieveLoan(loanId); assertEqualBigDecimal(BigDecimal.valueOf(3000), approvedData.getApprovedPrincipal()); - assertEqualBigDecimal(BigDecimal.valueOf(100), approvedData.getDiscountProposed()); - assertEqualBigDecimal(BigDecimal.valueOf(50), approvedData.getDiscountApproved()); - assertNull(approvedData.getDiscount()); + assertEqualBigDecimal(BigDecimal.valueOf(100), approvedData.getProposedDiscountFee()); + assertEqualBigDecimal(BigDecimal.valueOf(50), approvedData.getApprovedDiscountFee()); + assertNull(approvedData.getDiscountFee()); // Undo approval applicationHelper.undoApprovalById(loanId, WorkingCapitalLoanApplicationTestBuilder.buildUndoApproveRequest()); @@ -442,7 +442,7 @@ private GetWorkingCapitalLoansLoanIdResponse retrieveLoan(final Long loanId) { * test JVM and the server (which uses the tenant timezone). */ private LocalDate getSubmittedOnDate(final Long loanId) { - return retrieveLoan(loanId).getSubmittedOnDate(); + return retrieveLoan(loanId).getTimeline().getSubmittedOnDate(); } private Long createProduct() { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanDelinquencyActionIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanDelinquencyActionIntegrationTest.java index 7a5004c9bac..db4145f937b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanDelinquencyActionIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanDelinquencyActionIntegrationTest.java @@ -460,7 +460,7 @@ private Long submitAndApproveLoanWithExternalId(final Long clientId, final Long final LocalDate submittedOnDate = FeignCalls .ok(() -> FineractFeignClientHelper.getFineractFeignClient().workingCapitalLoans().retrieveWorkingCapitalLoanById(loanId)) - .getSubmittedOnDate(); + .getTimeline().getSubmittedOnDate(); applicationHelper.approveById(loanId, WorkingCapitalLoanApplicationTestBuilder.buildApproveRequest(submittedOnDate)); return loanId; } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanDisbursementTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanDisbursementTest.java index 2a06cce2659..63cb3f18b91 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanDisbursementTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanDisbursementTest.java @@ -301,10 +301,10 @@ public void testDisburseWithAllRequestFieldsAndVerifyResponse() { assertStatus(data, "loanStatusType.active"); assertNotNull(data.getBalance(), "GET loan after disburse should include balance"); assertEqualBigDecimal(transactionAmount.add(discountAmount), data.getBalance().getPrincipalOutstanding()); - assertEqualBigDecimal(discountAmount, data.getDiscount()); + assertEqualBigDecimal(discountAmount, data.getDiscountFee()); assertEquals(loanId, data.getId()); - assertNotNull(data.getClient()); - assertNotNull(data.getProduct()); + assertNotNull(data.getClientId()); + assertNotNull(data.getLoanProductId()); if (data.getTimeline() != null) { assertNotNull(data.getTimeline().getActualDisbursementDate()); @@ -318,7 +318,7 @@ public void testDisburseWithAllRequestFieldsAndVerifyResponse() { assertFalse(data.getDisbursementDetails().isEmpty(), "disbursementDetails should not be empty"); final GetDisbursementDetail disbursement = data.getDisbursementDetails().getFirst(); assertNotNull(disbursement.getExpectedDisbursementDate(), "disbursementDetails should include expectedDisbursementDate"); - assertNotNull(disbursement.getExpectedAmount(), "disbursementDetails should include expectedAmount"); + assertNotNull(disbursement.getPrincipal(), "disbursementDetails should include principal"); assertDateEquals(currentDate, disbursement.getActualDisbursementDate()); assertEqualBigDecimal(transactionAmount, disbursement.getActualAmount()); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignWorkingCapitalLoanRateChangeTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignWorkingCapitalLoanRateChangeTest.java index 4a59fdb3bdb..8a54ea5ed4c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignWorkingCapitalLoanRateChangeTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignWorkingCapitalLoanRateChangeTest.java @@ -77,7 +77,7 @@ void testUpdateRateOnActiveLoan() { GetWorkingCapitalLoansLoanIdResponse loan = wcLoanHelper.getLoanDetails(loanId); assertNotNull(loan); - assertEquals(0, BigDecimal.valueOf(17).compareTo(loan.getPeriodPaymentRate())); + assertEquals(0, BigDecimal.valueOf(17).compareTo(loan.getPaymentRate())); } @Test @@ -124,7 +124,7 @@ void testMultipleRateChangesAutoReversesPrevious() { assertTrue(firstChange.getReversed(), "Previous rate change should be auto-reversed"); GetWorkingCapitalLoansLoanIdResponse loan = wcLoanHelper.getLoanDetails(loanId); - assertEquals(0, BigDecimal.valueOf(15).compareTo(loan.getPeriodPaymentRate())); + assertEquals(0, BigDecimal.valueOf(15).compareTo(loan.getPaymentRate())); } @Test @@ -143,7 +143,7 @@ void testMultipleRateChangesOnDifferentBusinessDates() { wcLoanHelper.updateRate(loanId, WorkingCapitalLoanRequestBuilders.updateRate(BigDecimal.valueOf(11))); GetWorkingCapitalLoansLoanIdResponse loan = wcLoanHelper.getLoanDetails(loanId); - assertEquals(0, BigDecimal.valueOf(11).compareTo(loan.getPeriodPaymentRate()), + assertEquals(0, BigDecimal.valueOf(11).compareTo(loan.getPaymentRate()), "Rate should be updated to 11 after second rate change"); List history = wcLoanHelper.getRateChangeHistory(loanId); @@ -170,7 +170,7 @@ void testRateChangePastEndOfTermSucceeds() { wcLoanHelper.updateRate(loanId, WorkingCapitalLoanRequestBuilders.updateRate(BigDecimal.valueOf(15))); GetWorkingCapitalLoansLoanIdResponse loan = wcLoanHelper.getLoanDetails(loanId); - assertEquals(0, BigDecimal.valueOf(15).compareTo(loan.getPeriodPaymentRate()), + assertEquals(0, BigDecimal.valueOf(15).compareTo(loan.getPaymentRate()), "Rate should be updated to 15 after past-term rate change"); }); }