Skip to content

Commit

Permalink
FINERACT-2111: Performance issue on Retrieve Loan API - Transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
Jose Alberto Hernandez authored and adamsaghy committed Aug 2, 2024
1 parent 141e26f commit bd19f28
Show file tree
Hide file tree
Showing 21 changed files with 331 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,16 @@
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionReadService;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@RequiredArgsConstructor
public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainService {

private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
private final LoanTransactionReadService loanTransactionReadService;

@Override
@Transactional(readOnly = true)
Expand Down Expand Up @@ -262,7 +265,8 @@ private CollectionData calculateDelinquencyDataForOverdueInstallment(final Loan
final LoanRepaymentScheduleInstallment installment) {
final MonetaryCurrency loanCurrency = loan.getCurrency();
LoanRepaymentScheduleInstallment latestInstallment = loan.getLastLoanRepaymentScheduleInstallment();
List<LoanTransaction> chargebackTransactions = loan.getLoanTransactions(LoanTransaction::isChargeback);
List<LoanTransaction> chargebackTransactions = loanTransactionReadService.fetchLoanTransactionsByType(loan.getId(), null,
LoanTransactionType.CHARGEBACK.getValue());
LocalDate overdueSinceDate = null;
CollectionData collectionData = CollectionData.template();
BigDecimal outstandingAmount = BigDecimal.ZERO;
Expand Down Expand Up @@ -319,7 +323,8 @@ private CollectionData calculateDelinquencyDataForNonOverdueInstallment(final Lo
BigDecimal delinquentFee = BigDecimal.ZERO;
BigDecimal delinquentPenalty = BigDecimal.ZERO;

List<LoanTransaction> chargebackTransactions = loan.getLoanTransactions(LoanTransaction::isChargeback);
List<LoanTransaction> chargebackTransactions = loanTransactionReadService.fetchLoanTransactionsByType(loan.getId(), null,
LoanTransactionType.CHARGEBACK.getValue());
BigDecimal amountAvailable = installment.getTotalPaid(loanCurrency).getAmount();
for (LoanTransaction loanTransaction : chargebackTransactions) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionReadService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
Expand Down Expand Up @@ -88,7 +89,8 @@ public DelinquencyWritePlatformService delinquencyWritePlatformService(Delinquen

@Bean
@ConditionalOnMissingBean(LoanDelinquencyDomainService.class)
public LoanDelinquencyDomainService loanDelinquencyDomainService(DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper) {
return new LoanDelinquencyDomainServiceImpl(delinquencyEffectivePauseHelper);
public LoanDelinquencyDomainService loanDelinquencyDomainService(DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper,
LoanTransactionReadService loanTransactionReadService) {
return new LoanDelinquencyDomainServiceImpl(delinquencyEffectivePauseHelper, loanTransactionReadService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,30 @@
package org.apache.fineract.portfolio.loanaccount.data;

import java.math.BigDecimal;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.integration.annotation.Default;

@Data
@Getter
@RequiredArgsConstructor
public class LoanChargePaidByData {

private final Long id;
private final BigDecimal amount;
private final Integer installmentNumber;
private final Long chargeId;
private final Long transactionId;
private final String name;
private Long id;
private BigDecimal amount;
private Integer installmentNumber;
private Long chargeId;
private Long transactionId;
private String name;

@Default
public LoanChargePaidByData(Long id, BigDecimal amount, Integer installmentNumber, Long chargeId, Long transactionId, String name) {
this.id = id;
this.amount = amount;
this.installmentNumber = installmentNumber;
this.chargeId = chargeId;
this.transactionId = transactionId;
this.name = name;
}

public LoanChargePaidByData(final Long id, final BigDecimal amount, final Integer installmentNumber, final Long chargeId,
final Long transactionId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,8 @@ public void setLoanChargePaidByList(Collection<LoanChargePaidByData> loanChargeP
public void setLoanTransactionRelations(List<LoanTransactionRelationData> transactionRelations) {
this.transactionRelations = transactionRelations;
}

public boolean supportTransactionRelations() {
return !type.isAccrual();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,10 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface LoanTransactionRelationRepository
extends JpaRepository<LoanTransactionRelation, Long>, JpaSpecificationExecutor<LoanTransactionRelation> {

List<LoanTransactionRelation> findByFromTransaction(LoanTransaction fromTransaction);

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.service;
package org.apache.fineract.portfolio.loanaccount.mapper;

import java.util.List;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

public interface LoanChargePaidByReadPlatformService {
@Mapper(config = MapstructMapperConfig.class)
public interface LoanChargePaidByMapper {

@Mapping(target = "transactionId", source = "source.loanTransaction.id")
@Mapping(target = "chargeId", source = "source.loanCharge.id")
@Mapping(target = "name", source = "source.loanCharge.charge.name")
LoanChargePaidByData map(LoanChargePaidBy source);

List<LoanChargePaidByData> map(List<LoanChargePaidBy> sources);

List<LoanChargePaidByData> getLoanChargesPaidByTransactionId(Long id);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* 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.service;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanChargePaidByMapper;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class LoanChargePaidByReadService {

private final EntityManager entityManager;
private final LoanChargePaidByMapper loanChargePaidByMapper;

public List<LoanChargePaidByData> fetchLoanChargesPaidByDataTransactionId(Long transactionId) {
final List<Long> transactionIds = Arrays.asList(transactionId);
return fetchLoanChargesPaidByTransactionId(transactionIds).stream().map(loanChargePaidByMapper::map).toList();
}

public List<LoanChargePaidByData> fetchLoanChargesPaidByDataTransactionId(final List<Long> transactionIds) {
return fetchLoanChargesPaidByTransactionId(transactionIds).stream().map(loanChargePaidByMapper::map).toList();
}

public List<LoanChargePaidBy> fetchLoanChargesPaidByTransactionId(final List<Long> transactionIds) {

final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<LoanChargePaidBy> query = cb.createQuery(LoanChargePaidBy.class);

final Root<LoanChargePaidBy> root = query.from(LoanChargePaidBy.class);
root.fetch("loanTransaction", JoinType.INNER);
final Path<LoanTransaction> loanTransaction = root.join("loanTransaction", JoinType.INNER);

query.select(root).where(loanTransaction.get("id").in(transactionIds));

final List<Order> orders = new ArrayList<>();
orders.add(cb.desc(root.get("id")));
query.orderBy(orders);

final TypedQuery<LoanChargePaidBy> queryToExecute = entityManager.createQuery(query);
return queryToExecute.getResultList();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* 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.service;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class LoanTransactionReadService {

private final EntityManager entityManager;

public List<LoanTransaction> fetchLoanTransactionsByType(final Long loanId, final String externalId, final Integer transactionType) {
final List<Integer> transactionTypes = new ArrayList<>();
transactionTypes.add(transactionType);
return fetchLoanTransactionsByTypes(loanId, externalId, transactionTypes);
}

public List<LoanTransaction> fetchLoanTransactionsByTypes(final Long loanId, final String externalId,
final List<Integer> transactionTypes) {

final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<LoanTransaction> query = cb.createQuery(LoanTransaction.class);

final Root<LoanTransaction> root = query.from(LoanTransaction.class);
root.fetch("loan", JoinType.INNER);
final Path<Loan> loan = root.join("loan", JoinType.INNER);

Predicate loanPredicate = cb.equal(loan.get("id"), loanId);
if (externalId != null) {
loanPredicate = cb.equal(loan.get("externalId"), externalId);
}

query.select(root)
.where(cb.and(loanPredicate, root.get("typeOf").in(transactionTypes), cb.equal(root.get("reversed"), Boolean.FALSE)));

final List<Order> orders = new ArrayList<>();
orders.add(cb.desc(root.get("dateOf")));
orders.add(cb.desc(root.get("createdDate")));
orders.add(cb.desc(root.get("id")));
query.orderBy(orders);

final TypedQuery<LoanTransaction> queryToExecute = entityManager.createQuery(query);
return queryToExecute.getResultList();
}

}
Loading

0 comments on commit bd19f28

Please sign in to comment.