From ca3ae07a5de1b546bf36e8954a99c2553431d8d7 Mon Sep 17 00:00:00 2001 From: Jose Alberto Hernandez Date: Mon, 18 May 2026 22:34:30 -0500 Subject: [PATCH] FINERACT-2421: Working Capital loan details extended --- .../WorkingCapitalLoanApiResourceSwagger.java | 8 + .../data/WorkingCapitalLoanData.java | 6 + .../data/WorkingCapitalLoanSummaryData.java | 65 ++++++++ .../WorkingCapitalLoanTransactionData.java | 2 + .../mapper/WorkingCapitalLoanMapper.java | 8 +- .../WorkingCapitalLoanSummaryDataMapper.java | 145 ++++++++++++++++++ .../WorkingCapitalLoanTransactionMapper.java | 8 + ...oanApplicationReadPlatformServiceImpl.java | 21 +++ ...rkingCapitalLoanTransactionMapperTest.java | 21 +++ 9 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanSummaryData.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanSummaryDataMapper.java 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 b2530a50167..10699977ecb 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 @@ -197,6 +197,14 @@ private GetWorkingCapitalLoansLoanIdResponse() {} 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 = "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") + public BigDecimal dailyEir; + @Schema(example = "0.1691", description = "Annualized EIR: (1 + dailyEir)^365 − 1; null if schedule not yet generated") + public BigDecimal calculatedAnnualEir; @Schema(description = "Working capital breach)") public WorkingCapitalLoanProductApiResourceSwagger.GetWorkingCapitalLoanProductsResponse.GetWorkingCapitalLoanBreach breach; public WorkingCapitalLoanProductApiResourceSwagger.GetWorkingCapitalLoanNearBreach nearBreach; 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 76965d13beb..d71d1d69354 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,6 +54,7 @@ public class WorkingCapitalLoanData implements Serializable { private ExternalId externalId; private ClientData client; private Long officeId; + private String officeName; private Long fundId; private String fundName; private WorkingCapitalLoanProductData product; @@ -71,6 +72,10 @@ public class WorkingCapitalLoanData implements Serializable { private BigDecimal discount; private BigDecimal discountProposed; private BigDecimal discountApproved; + private Integer totalNoPayments; + private BigDecimal periodPaymentAmount; + private BigDecimal dailyEir; + private BigDecimal calculatedAnnualEir; private DelinquencyBucketData delinquencyBucket; private WorkingCapitalBreachData breach; private WorkingCapitalNearBreachData nearBreach; @@ -84,4 +89,5 @@ public class WorkingCapitalLoanData implements Serializable { private StringEnumOptionData delinquencyStartType; private WorkingCapitalLoanCollectionData collectionData; + private WorkingCapitalLoanSummaryData summary; } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanSummaryData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanSummaryData.java new file mode 100644 index 00000000000..5aec0c02248 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanSummaryData.java @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloan.data; + +import java.io.Serializable; +import java.math.BigDecimal; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.organisation.monetary.data.CurrencyData; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WorkingCapitalLoanSummaryData implements Serializable { + + private CurrencyData currency; + + // Principal + private BigDecimal principalDisbursed; + private BigDecimal principalPaid; + private BigDecimal principalOutstanding; + + // Discount fee + private BigDecimal discountCharged; + private BigDecimal discountPaid; + private BigDecimal discountOutstanding; + + // Income recognition + private BigDecimal realizedIncome; + private BigDecimal unrealizedIncome; + + // Overpayment + private BigDecimal overpaymentAmount; + + // Aggregates + private BigDecimal totalExpectedRepayment; + private BigDecimal totalRepayment; + private BigDecimal totalOutstanding; + + // Transaction summaries + private BigDecimal totalDisbursement; + private BigDecimal totalRepaymentTransaction; + private BigDecimal totalDiscountFee; +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanTransactionData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanTransactionData.java index 8e2d84fc325..5a89347b70d 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanTransactionData.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/WorkingCapitalLoanTransactionData.java @@ -28,6 +28,7 @@ import lombok.Setter; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData; import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData; @@ -40,6 +41,7 @@ public class WorkingCapitalLoanTransactionData implements Serializable { private Long id; private Long wcLoanId; + private CurrencyData currency; private LoanTransactionEnumData type; private LocalDate transactionDate; private LocalDate submittedOnDate; 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 8a5f2e45f4f..84a288c1e1f 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 @@ -49,12 +49,13 @@ @Mapper(config = MapstructMapperConfig.class, uses = { DelinquencyBucketMapper.class, WorkingCapitalLoanProductMapper.class, WorkingCapitalLoanBalanceMapper.class, WorkingCapitalLoanDisbursementDetailMapper.class, WorkingCapitalLoanTransactionMapper.class, - WorkingCapitalBreachMapper.class, WorkingCapitalNearBreachMapper.class }) + WorkingCapitalBreachMapper.class, WorkingCapitalNearBreachMapper.class, WorkingCapitalLoanSummaryDataMapper.class }) 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 = "fundId", source = "fund.id") @Mapping(target = "fundName", source = "fund.name") @Mapping(target = "product", source = "loanProduct") @@ -77,6 +78,11 @@ public interface WorkingCapitalLoanMapper { @Mapping(target = "delinquencyGraceDays", source = "loanProductRelatedDetails.delinquencyGraceDays") @Mapping(target = "delinquencyStartType", source = "loanProductRelatedDetails", qualifiedByName = "delinquencyStartTypeData") @Mapping(target = "collectionData", ignore = true) + @Mapping(target = "totalNoPayments", ignore = true) + @Mapping(target = "periodPaymentAmount", ignore = true) + @Mapping(target = "dailyEir", ignore = true) + @Mapping(target = "calculatedAnnualEir", ignore = true) + @Mapping(target = "summary", source = ".", qualifiedByName = "toSummaryData") WorkingCapitalLoanData toData(WorkingCapitalLoan loan); List toDataList(List loans); diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanSummaryDataMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanSummaryDataMapper.java new file mode 100644 index 00000000000..bd8e0f23ba0 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanSummaryDataMapper.java @@ -0,0 +1,145 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloan.mapper; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.infrastructure.core.service.MathUtil; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; +import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanSummaryData; +import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoan; +import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransaction; +import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransactionAllocation; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +@Mapper(config = MapstructMapperConfig.class) +public interface WorkingCapitalLoanSummaryDataMapper { + + @Named("toSummaryData") + @Mapping(target = "currency", source = ".", qualifiedByName = "toCurrency") + // Principal + @Mapping(target = "principalDisbursed", source = ".", qualifiedByName = "toPrincipalDisbursed") + @Mapping(target = "principalPaid", source = ".", qualifiedByName = "toPrincipalPaid") + @Mapping(target = "principalOutstanding", source = ".", qualifiedByName = "toPrincipalOutstanding") + // Discount fee + @Mapping(target = "discountCharged", source = ".", qualifiedByName = "toDiscountCharged") + @Mapping(target = "discountPaid", source = ".", qualifiedByName = "toDiscountPaid") + @Mapping(target = "discountOutstanding", source = ".", qualifiedByName = "toDiscountOutstanding") + // Income recognition + @Mapping(target = "realizedIncome", source = ".", qualifiedByName = "toRealizedIncome") + @Mapping(target = "unrealizedIncome", source = ".", qualifiedByName = "toUnrealizedIncome") + // Overpayment + @Mapping(target = "overpaymentAmount", source = ".", qualifiedByName = "toOverpaymentAmount") + // Aggregates + @Mapping(target = "totalExpectedRepayment", source = ".", qualifiedByName = "toTotalExpectedRepayment") + @Mapping(target = "totalRepayment", source = ".", qualifiedByName = "toTotalRepayment") + @Mapping(target = "totalOutstanding", source = ".", qualifiedByName = "toTotalOutstanding") + // Transaction summaries + @Mapping(target = "totalDisbursement", source = ".", qualifiedByName = "toPrincipalDisbursed") + @Mapping(target = "totalRepaymentTransaction", source = ".", qualifiedByName = "toTotalRepaymentTransaction") + @Mapping(target = "totalDiscountFee", source = ".", qualifiedByName = "toDiscountCharged") + WorkingCapitalLoanSummaryData toData(WorkingCapitalLoan loan); + + @Named("toCurrency") + default CurrencyData toCurrency(final WorkingCapitalLoan loan) { + return loan.getLoanProduct().getCurrency().toData(); + } + + @Named("toPrincipalDisbursed") + default BigDecimal toPrincipalDisbursed(final WorkingCapitalLoan loan) { + return sumActive(loan.getTransactions(), LoanTransactionType.DISBURSEMENT); + } + + @Named("toPrincipalPaid") + default BigDecimal toPrincipalPaid(final WorkingCapitalLoan loan) { + return loan.getBalance() != null ? MathUtil.nullToZero(loan.getBalance().getTotalPaidPrincipal()) : BigDecimal.ZERO; + } + + @Named("toPrincipalOutstanding") + default BigDecimal toPrincipalOutstanding(final WorkingCapitalLoan loan) { + return loan.getBalance() != null ? MathUtil.nullToZero(loan.getBalance().getPrincipalOutstanding()) : BigDecimal.ZERO; + } + + @Named("toDiscountCharged") + default BigDecimal toDiscountCharged(final WorkingCapitalLoan loan) { + return sumActive(loan.getTransactions(), LoanTransactionType.DISCOUNT_FEE); + } + + @Named("toDiscountPaid") + default BigDecimal toDiscountPaid(final WorkingCapitalLoan loan) { + return sumActiveAllocationField(loan.getTransactions(), WorkingCapitalLoanTransactionAllocation::getFeeChargesPortion); + } + + @Named("toDiscountOutstanding") + default BigDecimal toDiscountOutstanding(final WorkingCapitalLoan loan) { + return toDiscountCharged(loan).subtract(toDiscountPaid(loan)); + } + + @Named("toRealizedIncome") + default BigDecimal toRealizedIncome(final WorkingCapitalLoan loan) { + return loan.getBalance() != null ? MathUtil.nullToZero(loan.getBalance().getRealizedIncome()) : BigDecimal.ZERO; + } + + @Named("toUnrealizedIncome") + default BigDecimal toUnrealizedIncome(final WorkingCapitalLoan loan) { + return loan.getBalance() != null ? MathUtil.nullToZero(loan.getBalance().getUnrealizedIncome()) : BigDecimal.ZERO; + } + + @Named("toOverpaymentAmount") + default BigDecimal toOverpaymentAmount(final WorkingCapitalLoan loan) { + return loan.getBalance() != null ? MathUtil.nullToZero(loan.getBalance().getOverpaymentAmount()) : BigDecimal.ZERO; + } + + @Named("toTotalExpectedRepayment") + default BigDecimal toTotalExpectedRepayment(final WorkingCapitalLoan loan) { + return toPrincipalDisbursed(loan).add(toDiscountCharged(loan)); + } + + @Named("toTotalRepayment") + default BigDecimal toTotalRepayment(final WorkingCapitalLoan loan) { + return loan.getBalance() != null ? MathUtil.nullToZero(loan.getBalance().getTotalPayment()) : BigDecimal.ZERO; + } + + @Named("toTotalOutstanding") + default BigDecimal toTotalOutstanding(final WorkingCapitalLoan loan) { + return toPrincipalOutstanding(loan).add(toDiscountOutstanding(loan)); + } + + @Named("toTotalRepaymentTransaction") + default BigDecimal toTotalRepaymentTransaction(final WorkingCapitalLoan loan) { + return sumActive(loan.getTransactions(), LoanTransactionType.REPAYMENT); + } + + private BigDecimal sumActive(final List transactions, final LoanTransactionType type) { + return transactions.stream().filter(t -> t.getTypeOf() == type && !t.isReversed()) + .map(WorkingCapitalLoanTransaction::getTransactionAmount).reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private BigDecimal sumActiveAllocationField(final List transactions, + final Function extractor) { + return transactions.stream().filter(t -> !t.isReversed() && t.getAllocation() != null).map(t -> extractor.apply(t.getAllocation())) + .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanTransactionMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanTransactionMapper.java index 305867e8d94..78f3bc2c439 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanTransactionMapper.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanTransactionMapper.java @@ -21,12 +21,14 @@ import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionData; +import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoan; import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransaction; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -43,6 +45,7 @@ public interface WorkingCapitalLoanTransactionMapper { @Mapping(target = "principalPortion", source = "allocation.principalPortion") @Mapping(target = "feeChargesPortion", source = "allocation.feeChargesPortion") @Mapping(target = "penaltyChargesPortion", source = "allocation.penaltyChargesPortion") + @Mapping(target = "currency", source = "wcLoan", qualifiedByName = "currencyData") WorkingCapitalLoanTransactionData toData(WorkingCapitalLoanTransaction transaction); @Named("loanTransactionTypeToEnumData") @@ -64,4 +67,9 @@ default PaymentDetailData paymentDetailToData(final PaymentDetail paymentDetail) default CodeValueData codeValueToData(final CodeValue codeValue) { return codeValue == null ? null : CodeValueData.instance(codeValue.getId(), codeValue.getLabel()); } + + @Named("currencyData") + default CurrencyData currencyData(final WorkingCapitalLoan wcLoan) { + return wcLoan.getLoanProduct().getCurrency().toData(); + } } 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 55a231c1bfd..e949adceb7b 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 @@ -19,6 +19,8 @@ package org.apache.fineract.portfolio.workingcapitalloan.service; import jakarta.persistence.criteria.Predicate; +import java.math.BigDecimal; +import java.math.MathContext; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -31,6 +33,8 @@ import org.apache.fineract.infrastructure.core.data.StringEnumOptionData; 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.MoneyHelper; import org.apache.fineract.portfolio.accountdetails.data.WorkingCapitalLoanAccountSummaryData; import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData; @@ -73,6 +77,7 @@ public class WorkingCapitalLoanApplicationReadPlatformServiceImpl implements Wor private final WorkingCapitalBreachReadPlatformService breachReadPlatformService; private final WorkingCapitalLoanDelinquencyReadPlatformService workingCapitalLoanDelinquencyReadPlatformService; private final WorkingCapitalNearBreachReadPlatformService nearBreachReadPlatformService; + private final ProjectedAmortizationScheduleRepositoryWrapper scheduleRepositoryWrapper; @Override public WorkingCapitalLoanTemplateData retrieveTemplate(final Long productId, final Long clientId) { @@ -162,6 +167,7 @@ public WorkingCapitalLoanData retrieveOne(final Long loanId) { WorkingCapitalLoanCollectionData collectionData = workingCapitalLoanDelinquencyReadPlatformService.getCollectionData(loanId, ThreadLocalContextUtil.getBusinessDate()); data.setCollectionData(collectionData); + enrichWithRateAndTerm(loan, data); return data; } @@ -175,9 +181,24 @@ public WorkingCapitalLoanData retrieveOne(final ExternalId externalId) { WorkingCapitalLoanCollectionData collectionData = workingCapitalLoanDelinquencyReadPlatformService.getCollectionData(loan.getId(), ThreadLocalContextUtil.getBusinessDate()); data.setCollectionData(collectionData); + enrichWithRateAndTerm(loanWithDetails, data); return data; } + 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.setPeriodPaymentAmount(model.expectedPaymentAmount() != null ? model.expectedPaymentAmount().getAmount() : null); + data.setDailyEir(dailyEir); + if (dailyEir != null) { + data.setCalculatedAnnualEir(BigDecimal.ONE.add(dailyEir, mc).pow(365, mc).subtract(BigDecimal.ONE, mc)); + } + }); + } + @Override public Long getResolvedLoanId(final ExternalId externalId) { return this.repository.findByExternalId(externalId).map(WorkingCapitalLoan::getId).orElse(null); diff --git a/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanTransactionMapperTest.java b/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanTransactionMapperTest.java index e8286c21421..3daba391b42 100644 --- a/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanTransactionMapperTest.java +++ b/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/WorkingCapitalLoanTransactionMapperTest.java @@ -26,10 +26,13 @@ import java.math.BigDecimal; import java.time.LocalDate; import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.workingcapitalloan.data.WorkingCapitalLoanTransactionData; +import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoan; import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransaction; import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoanTransactionAllocation; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mapstruct.factory.Mappers; @@ -47,6 +50,14 @@ class WorkingCapitalLoanTransactionMapperTest { @Mock private WorkingCapitalLoanTransactionAllocation allocation; + @Mock + private WorkingCapitalLoan wcLoan; + + @Mock + private WorkingCapitalLoanProduct wcProduct; + + private final MonetaryCurrency currency = new MonetaryCurrency("USD", 2, null); + @Test void toData_mapsAllFieldsIncludingAllocationPortions() { final LocalDate txnDate = LocalDate.of(2024, 2, 1); @@ -65,11 +76,16 @@ void toData_mapsAllFieldsIncludingAllocationPortions() { when(allocation.getFeeChargesPortion()).thenReturn(null); when(allocation.getPenaltyChargesPortion()).thenReturn(null); + when(transaction.getWcLoan()).thenReturn(wcLoan); + when(wcLoan.getLoanProduct()).thenReturn(wcProduct); + when(wcProduct.getCurrency()).thenReturn(currency); + final WorkingCapitalLoanTransactionData data = mapper.toData(transaction); assertNotNull(data); assertEquals(1L, data.getId()); assertNotNull(data.getType()); + assertNotNull(data.getCurrency()); assertEquals(LoanTransactionType.DISBURSEMENT.getValue().longValue(), data.getType().getId()); assertEquals(LoanTransactionType.DISBURSEMENT.getCode(), data.getType().getCode()); assertEquals(txnDate, data.getTransactionDate()); @@ -94,10 +110,15 @@ void toData_whenAllocationNull_setsPortionsToNull() { when(transaction.getReversedOnDate()).thenReturn(null); when(transaction.getAllocation()).thenReturn(null); + when(transaction.getWcLoan()).thenReturn(wcLoan); + when(wcLoan.getLoanProduct()).thenReturn(wcProduct); + when(wcProduct.getCurrency()).thenReturn(currency); + final WorkingCapitalLoanTransactionData data = mapper.toData(transaction); assertNotNull(data); assertNotNull(data.getType()); + assertNotNull(data.getCurrency()); assertEquals(LoanTransactionType.DISBURSEMENT.getCode(), data.getType().getCode()); assertNull(data.getPrincipalPortion()); assertNull(data.getFeeChargesPortion());