FINERACT-2111: Performance issue on Retrieve Loan API - Transactions

This commit is contained in:
Jose Alberto Hernandez
2024-08-02 22:48:01 +02:00
committed by Adam Saghy
parent 141e26fda7
commit bd19f28cb6
21 changed files with 331 additions and 154 deletions
@@ -35,6 +35,8 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanDelinquencyData;
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
@@ -42,6 +44,7 @@ import org.springframework.transaction.annotation.Transactional;
public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainService {
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
private final LoanTransactionReadService loanTransactionReadService;
@Override
@Transactional(readOnly = true)
@@ -262,7 +265,8 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
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;
@@ -319,7 +323,8 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe
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) {
@@ -42,6 +42,7 @@ import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketPars
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;
@@ -88,7 +89,8 @@ public class DelinquencyConfiguration {
@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);
}
}
@@ -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) {
@@ -381,4 +381,8 @@ public class LoanTransactionData {
public void setLoanTransactionRelations(List<LoanTransactionRelationData> transactionRelations) {
this.transactionRelations = transactionRelations;
}
public boolean supportTransactionRelations() {
return !type.isAccrual();
}
}
@@ -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);
}
@@ -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);
}
@@ -1,68 +0,0 @@
/**
* 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 java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
@RequiredArgsConstructor
public class LoanChargePaidByReadPlatformServiceImpl implements LoanChargePaidByReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
@Override
public List<LoanChargePaidByData> getLoanChargesPaidByTransactionId(Long transactionId) {
this.context.authenticatedUser();
final LoanChargePaidByMapper rm = new LoanChargePaidByMapper();
final String sql = "select " + rm.loanChargePaidBySchema() + " where lcpd.loan_transaction_id = ?";
return this.jdbcTemplate.query(sql, rm, transactionId); // NOSONAR
}
private static final class LoanChargePaidByMapper implements RowMapper<LoanChargePaidByData> {
public String loanChargePaidBySchema() {
return "lcpd.id as id, lcpd.amount as amount, lcpd.installment_number as installmentNumber,"
+ " lcpd.loan_charge_id as chargeId, lcpd.loan_transaction_id as transactionId, " + " c.name as chargeName"
+ " from m_loan_charge_paid_by lcpd" + " join m_loan_charge lc on lc.id=lcpd.loan_charge_Id"
+ " join m_charge c on c.id=lc.charge_id";
}
@Override
public LoanChargePaidByData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final BigDecimal amount = rs.getBigDecimal("amount");
final Integer installmentNumber = rs.getInt("installmentNumber");
final Long chargeId = rs.getLong("chargeId");
final Long transactionId = rs.getLong("transactionId");
final String chargeName = rs.getString("chargeName");
return new LoanChargePaidByData(id, amount, installmentNumber, chargeId, transactionId, chargeName);
}
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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.LoanTransactionRelationData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionRelationMapper;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class LoanTransactionRelationReadService {
private final EntityManager entityManager;
private final LoanTransactionRelationMapper loanTransactionRelationMapper;
public List<LoanTransactionRelationData> fetchLoanTransactionRelationDataFrom(final Long transactionId) {
final List<Long> transactionIds = Arrays.asList(transactionId);
return fetchLoanTransactionRelationFrom(transactionIds).stream().map(loanTransactionRelationMapper::map).toList();
}
public List<LoanTransactionRelationData> fetchLoanTransactionRelationDataFrom(final List<Long> transactionIds) {
return fetchLoanTransactionRelationFrom(transactionIds).stream().map(loanTransactionRelationMapper::map).toList();
}
public List<LoanTransactionRelation> fetchLoanTransactionRelationFrom(final List<Long> transactionIds) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<LoanTransactionRelation> query = cb.createQuery(LoanTransactionRelation.class);
final Root<LoanTransactionRelation> root = query.from(LoanTransactionRelation.class);
root.fetch("fromTransaction", JoinType.INNER);
final Path<LoanTransaction> fromTransaction = root.join("fromTransaction", JoinType.INNER);
query.select(root).where(fromTransaction.get("id").in(transactionIds));
final List<Order> orders = new ArrayList<>();
orders.add(cb.desc(root.get("id")));
query.orderBy(orders);
final TypedQuery<LoanTransactionRelation> queryToExecute = entityManager.createQuery(query);
return queryToExecute.getResultList();
}
}