FINERACT-2235: add summary data to loans list api
This commit is contained in:
committed by
Arnold Gálovics
parent
6455311f6c
commit
12617ea1df
+1
@@ -40,6 +40,7 @@ public final class DataTableApiConstant {
|
||||
// associationParameters
|
||||
public static final String allAssociateParamName = "all";
|
||||
public static final String repaymentScheduleAssociateParamName = "repaymentSchedule";
|
||||
public static final String summaryAssociateParamName = "summary";
|
||||
public static final String originalScheduleAssociateParamName = "originalSchedule";
|
||||
public static final String transactionsAssociateParamName = "transactions";
|
||||
public static final String chargesAssociateParamName = "charges";
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.loanaccount.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
|
||||
@Getter
|
||||
public class LoanTransactionBalanceWithLoanId extends LoanTransactionBalance {
|
||||
|
||||
private final Long loanId;
|
||||
|
||||
public LoanTransactionBalanceWithLoanId(LoanTransactionType transactionType, boolean reversed, boolean manuallyAdjustedOrReversed,
|
||||
BigDecimal amount, Long loanId) {
|
||||
super(transactionType, reversed, manuallyAdjustedOrReversed, amount);
|
||||
this.loanId = loanId;
|
||||
}
|
||||
}
|
||||
+20
-9
@@ -26,11 +26,14 @@ import jakarta.persistence.criteria.Path;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.jpa.CriteriaQueryFactory;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionBalance;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionBalanceWithLoanId;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -41,10 +44,16 @@ public class LoanSummaryBalancesRepository {
|
||||
private final EntityManager entityManager;
|
||||
private final CriteriaQueryFactory criteriaQueryFactory;
|
||||
|
||||
public Collection<LoanTransactionBalance> retrieveLoanSummaryBalancesByTransactionType(final Long loanId,
|
||||
public Collection<LoanTransactionBalanceWithLoanId> retrieveLoanSummaryBalancesByTransactionType(final Long loanId,
|
||||
final List<LoanTransactionType> transactionTypes) {
|
||||
return retrieveLoanSummaryBalancesByTransactionType(Arrays.asList(loanId), transactionTypes).getOrDefault(loanId,
|
||||
new ArrayList<>());
|
||||
}
|
||||
|
||||
public Map<Long, List<LoanTransactionBalanceWithLoanId>> retrieveLoanSummaryBalancesByTransactionType(final List<Long> loanIds,
|
||||
final List<LoanTransactionType> transactionTypes) {
|
||||
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<LoanTransactionBalance> query = cb.createQuery(LoanTransactionBalance.class);
|
||||
CriteriaQuery<LoanTransactionBalanceWithLoanId> query = cb.createQuery(LoanTransactionBalanceWithLoanId.class);
|
||||
|
||||
Root<LoanTransaction> root = query.from(LoanTransaction.class);
|
||||
|
||||
@@ -52,7 +61,7 @@ public class LoanSummaryBalancesRepository {
|
||||
Path<Loan> la = r.get("loan");
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
predicates.add(cb.equal(la.get("id"), loanId)); // Loan
|
||||
predicates.add(la.get("id").in(loanIds)); // Loans
|
||||
predicates.add(r.get("typeOf").in(transactionTypes)); // Transaction Type
|
||||
|
||||
return cb.and(predicates.toArray(new Predicate[0]));
|
||||
@@ -60,12 +69,14 @@ public class LoanSummaryBalancesRepository {
|
||||
|
||||
criteriaQueryFactory.applySpecificationToCriteria(root, spec, query);
|
||||
|
||||
query.groupBy(root.get("typeOf"), root.get("reversed"), root.get("manuallyAdjustedOrReversed"));
|
||||
query.select(cb.construct(LoanTransactionBalance.class, root.get("typeOf"), root.get("reversed"),
|
||||
root.get("manuallyAdjustedOrReversed"), cb.sum(root.get("amount"))));
|
||||
query.groupBy(root.get("typeOf"), root.get("reversed"), root.get("manuallyAdjustedOrReversed"), root.get("loan").get("id"));
|
||||
query.select(cb.construct(LoanTransactionBalanceWithLoanId.class, root.get("typeOf"), root.get("reversed"),
|
||||
root.get("manuallyAdjustedOrReversed"), cb.sum(root.get("amount")), root.get("loan").get("id")));
|
||||
|
||||
TypedQuery<LoanTransactionBalance> queryToExecute = entityManager.createQuery(query);
|
||||
return queryToExecute.getResultList();
|
||||
TypedQuery<LoanTransactionBalanceWithLoanId> queryToExecute = entityManager.createQuery(query);
|
||||
Map<Long, List<LoanTransactionBalanceWithLoanId>> collect = queryToExecute.getResultList().stream()
|
||||
.collect(Collectors.groupingBy(LoanTransactionBalanceWithLoanId::getLoanId));
|
||||
return collect;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-1
@@ -478,6 +478,7 @@ public class LoansApiResource {
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
|
||||
@QueryParam("accountNo") @Parameter(description = "accountNo") final String accountNo,
|
||||
@QueryParam("associations") @Parameter(description = "associations") final String associations,
|
||||
@QueryParam("status") @Parameter(description = "status") final String status) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
@@ -490,7 +491,23 @@ public class LoansApiResource {
|
||||
.externalId(externalId).offset(offset).limit(limit).orderBy(orderBy).status(status).build();
|
||||
|
||||
final Page<LoanAccountData> loanBasicDetails = this.loanReadPlatformService.retrieveAll(searchParameters);
|
||||
|
||||
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||
if (associationParameters.contains(DataTableApiConstant.summaryAssociateParamName)) {
|
||||
loanBasicDetails.getPageItems().forEach(i -> {
|
||||
Collection<DisbursementData> disbursementData = this.loanReadPlatformService.retrieveLoanDisbursementDetails(i.getId());
|
||||
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = new RepaymentScheduleRelatedLoanData(
|
||||
i.getTimeline().getExpectedDisbursementDate(), i.getTimeline().getActualDisbursementDate(), i.getCurrency(),
|
||||
i.getPrincipal(), i.getInArrearsTolerance(), i.getFeeChargesAtDisbursementCharged());
|
||||
final LoanScheduleData repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(i.getId(),
|
||||
repaymentScheduleRelatedData, disbursementData, i.isInterestRecalculationEnabled(),
|
||||
LoanScheduleType.fromEnumOptionData(i.getLoanScheduleType()));
|
||||
LoanSummaryDataProvider loanSummaryDataProvider = loanSummaryProviderDelegate
|
||||
.resolveLoanSummaryDataProvider(i.getTransactionProcessingStrategyCode());
|
||||
i.setSummary(loanSummaryDataProvider.withTransactionAmountsSummary(i.getId(), i.getSummary(), repaymentSchedule,
|
||||
loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(i.getId(),
|
||||
LoanApiConstants.LOAN_SUMMARY_TRANSACTION_TYPES)));
|
||||
});
|
||||
}
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, loanBasicDetails, LOAN_DATA_PARAMETERS);
|
||||
}
|
||||
|
||||
+6
-6
@@ -35,7 +35,7 @@ public abstract class CommonLoanSummaryDataProvider implements LoanSummaryDataPr
|
||||
|
||||
@Override
|
||||
public LoanSummaryData withTransactionAmountsSummary(Loan loan, LoanSummaryData defaultSummaryData, LoanScheduleData repaymentSchedule,
|
||||
Collection<LoanTransactionBalance> loanTransactionBalances) {
|
||||
Collection<? extends LoanTransactionBalance> loanTransactionBalances) {
|
||||
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
||||
|
||||
BigDecimal totalMerchantRefund = BigDecimal.ZERO;
|
||||
@@ -137,16 +137,16 @@ public abstract class CommonLoanSummaryDataProvider implements LoanSummaryDataPr
|
||||
.totalUnpaidPayableNotDueInterest(totalUnpaidPayableNotDueInterest).totalInterestRefund(totalInterestRefund).build();
|
||||
}
|
||||
|
||||
private static BigDecimal fetchLoanTransactionBalanceByType(final Collection<LoanTransactionBalance> loanTransactionBalances,
|
||||
private static BigDecimal fetchLoanTransactionBalanceByType(final Collection<? extends LoanTransactionBalance> loanTransactionBalances,
|
||||
final LoanTransactionType transactionType) {
|
||||
final Optional<LoanTransactionBalance> optLoanTransactionBalance = loanTransactionBalances.stream()
|
||||
final Optional<? extends LoanTransactionBalance> optLoanTransactionBalance = loanTransactionBalances.stream()
|
||||
.filter(balance -> balance.getTransactionType().equals(transactionType) && !balance.isReversed()).findFirst();
|
||||
return optLoanTransactionBalance.isPresent() ? optLoanTransactionBalance.get().getAmount() : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private static BigDecimal fetchLoanTransactionBalanceReversedByType(final Collection<LoanTransactionBalance> loanTransactionBalances,
|
||||
final LoanTransactionType transactionType) {
|
||||
final Optional<LoanTransactionBalance> optLoanTransactionBalance = loanTransactionBalances.stream()
|
||||
private static BigDecimal fetchLoanTransactionBalanceReversedByType(
|
||||
final Collection<? extends LoanTransactionBalance> loanTransactionBalances, final LoanTransactionType transactionType) {
|
||||
final Optional<? extends LoanTransactionBalance> optLoanTransactionBalance = loanTransactionBalances.stream()
|
||||
.filter(balance -> balance.getTransactionType().equals(transactionType) && balance.isReversed()
|
||||
&& balance.isManuallyAdjustedOrReversed())
|
||||
.findFirst();
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ public class CumulativeLoanSummaryDataProvider extends CommonLoanSummaryDataProv
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public LoanSummaryData withTransactionAmountsSummary(Long loanId, LoanSummaryData defaultSummaryData,
|
||||
LoanScheduleData repaymentSchedule, Collection<LoanTransactionBalance> loanTransactionBalances) {
|
||||
LoanScheduleData repaymentSchedule, Collection<? extends LoanTransactionBalance> loanTransactionBalances) {
|
||||
Loan loan = null;
|
||||
return super.withTransactionAmountsSummary(loan, defaultSummaryData, repaymentSchedule, loanTransactionBalances);
|
||||
}
|
||||
|
||||
+2
-2
@@ -38,10 +38,10 @@ public interface LoanSummaryDataProvider {
|
||||
LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData);
|
||||
|
||||
LoanSummaryData withTransactionAmountsSummary(Long loanId, LoanSummaryData defaultSummaryData, LoanScheduleData repaymentSchedule,
|
||||
Collection<LoanTransactionBalance> loanTransactionBalances);
|
||||
Collection<? extends LoanTransactionBalance> loanTransactionBalances);
|
||||
|
||||
LoanSummaryData withTransactionAmountsSummary(Loan loan, LoanSummaryData defaultSummaryData, LoanScheduleData repaymentSchedule,
|
||||
Collection<LoanTransactionBalance> loanTransactionBalances);
|
||||
Collection<? extends LoanTransactionBalance> loanTransactionBalances);
|
||||
|
||||
boolean accept(String loanProcessingStrategyCode);
|
||||
}
|
||||
|
||||
+2
-2
@@ -62,14 +62,14 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public LoanSummaryData withTransactionAmountsSummary(Long loanId, LoanSummaryData defaultSummaryData,
|
||||
LoanScheduleData repaymentSchedule, Collection<LoanTransactionBalance> loanTransactionBalances) {
|
||||
LoanScheduleData repaymentSchedule, Collection<? extends LoanTransactionBalance> loanTransactionBalances) {
|
||||
final Loan loan = loanRepository.findOneWithNotFoundDetection(loanId, true);
|
||||
return super.withTransactionAmountsSummary(loan, defaultSummaryData, repaymentSchedule, loanTransactionBalances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSummaryData withTransactionAmountsSummary(Loan loan, LoanSummaryData defaultSummaryData, LoanScheduleData repaymentSchedule,
|
||||
Collection<LoanTransactionBalance> loanTransactionBalances) {
|
||||
Collection<? extends LoanTransactionBalance> loanTransactionBalances) {
|
||||
return super.withTransactionAmountsSummary(loan, defaultSummaryData, repaymentSchedule, loanTransactionBalances);
|
||||
}
|
||||
|
||||
|
||||
@@ -11147,6 +11147,19 @@ GET https://DomainName/api/v1/loans/{loanId}
|
||||
</dd>
|
||||
<dd>Use account no. of loans to restrict results.</dd>
|
||||
|
||||
<dt>associations</dt>
|
||||
<dd>String <span>optional,</span>
|
||||
<span>Comma separated list of recurring loan 'associations' (itemized below).</span>
|
||||
</dd>
|
||||
<dd>
|
||||
<br>
|
||||
Associations are just extra pieces of data that you might or might not want to retrieve.<br>
|
||||
<br>
|
||||
</dd>
|
||||
<dd>
|
||||
<b>'summary':</b> Gets loan account summarry related information.
|
||||
</dd>
|
||||
|
||||
<dt>externalId</dt>
|
||||
<dd>
|
||||
String <span>optional</span>
|
||||
|
||||
+6
@@ -64,6 +64,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansResponse;
|
||||
import org.apache.fineract.client.models.PaymentTypeData;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
@@ -1607,6 +1608,11 @@ public class LoanTransactionHelper {
|
||||
excludedTransactionTypes, page, size, sort));
|
||||
}
|
||||
|
||||
public GetLoansResponse retrieveAllLoans(final String accountNumber, final String associations) {
|
||||
return Calls.ok(
|
||||
FineractClientHelper.getFineractClient().loans.retrieveAll27(null, 0, 10, null, null, accountNumber, associations, null));
|
||||
}
|
||||
|
||||
// TODO: Rewrite to use fineract-client instead!
|
||||
// Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
|
||||
// org.apache.fineract.client.models.PostLoansLoanIdRequest)
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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.integrationtests.loan;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoansRequest;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LoanApiIntegrationTest extends BaseLoanIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test_retrieveLoansWithSummary_Works() {
|
||||
AtomicLong createdLoanId = new AtomicLong();
|
||||
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 3;
|
||||
int repaymentEvery = 1;
|
||||
|
||||
// Create Loan Product
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
|
||||
.numberOfRepayments(numberOfRepayments) //
|
||||
.repaymentEvery(repaymentEvery) //
|
||||
.installmentAmountInMultiplesOf(null) //
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()) //
|
||||
.interestType(InterestType.DECLINING_BALANCE)//
|
||||
.interestRatePerPeriod(10.0)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||
.interestRecalculationCompoundingMethod(InterestRecalculationCompoundingMethod.NONE)//
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD)//
|
||||
.isInterestRecalculationEnabled(true)//
|
||||
.recalculationRestFrequencyInterval(1)//
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY)//
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.REDUCE_EMI_AMOUNT)//
|
||||
.allowPartialPeriodInterestCalcualtion(false)//
|
||||
.disallowExpectedDisbursements(false)//
|
||||
.allowApprovedDisbursedAmountsOverApplied(false)//
|
||||
.overAppliedNumber(null)//
|
||||
.overAppliedCalculationType(null)//
|
||||
.multiDisburseLoan(null);//
|
||||
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
// Apply and Approve Loan
|
||||
double amount = 5000.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
|
||||
.repaymentEvery(repaymentEvery)//
|
||||
.interestRatePerPeriod(BigDecimal.valueOf(10.0))//
|
||||
.loanTermFrequency(numberOfRepayments)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||
.loanTermFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||
.interestType(InterestType.DECLINING_BALANCE)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY);//
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(amount, "01 January 2023"));
|
||||
|
||||
Long loanId = approvedLoanResult.getLoanId();
|
||||
createdLoanId.getAndSet(loanId);
|
||||
|
||||
// disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(amount), "01 January 2023");
|
||||
});
|
||||
runAt("01 February 2023", () -> {
|
||||
long loanId = createdLoanId.get();
|
||||
GetLoansLoanIdResponse loanResponse = loanTransactionHelper.getLoanDetails(loanId);
|
||||
GetLoansResponse loansLoanIdResponse = loanTransactionHelper.retrieveAllLoans(loanResponse.getAccountNo(), "summary");
|
||||
BigDecimal totalUnpaidPayableDueInterest = loansLoanIdResponse.getPageItems().iterator().next().getSummary()
|
||||
.getTotalUnpaidPayableDueInterest();
|
||||
assertThat(totalUnpaidPayableDueInterest).isEqualByComparingTo(BigDecimal.valueOf(509.59));
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user