diff --git a/fineract-core/dependencies.gradle b/fineract-core/dependencies.gradle index b7bab1d2c78..23a5f6277ad 100644 --- a/fineract-core/dependencies.gradle +++ b/fineract-core/dependencies.gradle @@ -128,4 +128,10 @@ dependencies { implementation( project(path: ':fineract-avro-schemas') ) + implementation('io.github.openfeign.querydsl:querydsl-core:6.2.1', + 'io.github.openfeign.querydsl:querydsl-jpa:6.2.1') + annotationProcessor( + 'io.github.openfeign.querydsl:querydsl-apt:6.2.1:jakarta', + 'jakarta.persistence:jakarta.persistence-api:3.1.0', + ) } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/JdbcSupport.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/JdbcSupport.java index b37fc159f4b..4e5505630ad 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/JdbcSupport.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/JdbcSupport.java @@ -117,7 +117,7 @@ public static BigDecimal getBigDecimalDefaultToNullIfZero(final ResultSet rs, fi return defaultToNullIfZero(value); } - private static BigDecimal defaultToNullIfZero(final BigDecimal value) { + public static BigDecimal defaultToNullIfZero(final BigDecimal value) { BigDecimal result = value; if (value != null && BigDecimal.ZERO.compareTo(value) == 0) { result = null; diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java index 51b394b1506..050f9d91a7f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java @@ -55,6 +55,10 @@ public Page fetchPage(final JdbcTemplate jt, final String sqlFetchRows, f return new Page<>(items, totalFilteredRecords); } + public Page createPageFromItems(final List items, final Long filteredRecords) { + return new Page<>(items, filteredRecords.intValue()); + } + public Page fetchPage(JdbcTemplate jdbcTemplate, String sql, Class type) { final List items = jdbcTemplate.queryForList(sql, type); diff --git a/fineract-document/dependencies.gradle b/fineract-document/dependencies.gradle index 0f4c8facd23..631d2020a7d 100644 --- a/fineract-document/dependencies.gradle +++ b/fineract-document/dependencies.gradle @@ -68,6 +68,12 @@ dependencies { implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') { exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence' } + implementation('io.github.openfeign.querydsl:querydsl-core:6.2.1', + 'io.github.openfeign.querydsl:querydsl-jpa:6.2.1') + annotationProcessor( + 'io.github.openfeign.querydsl:querydsl-apt:6.2.1:jakarta', + 'jakarta.persistence:jakarta.persistence-api:3.1.0', + ) // testCompile dependencies are ONLY used in src/test, not src/main. // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! // diff --git a/fineract-loan/dependencies.gradle b/fineract-loan/dependencies.gradle index 467fc305f44..dc27efc41cb 100644 --- a/fineract-loan/dependencies.gradle +++ b/fineract-loan/dependencies.gradle @@ -60,6 +60,12 @@ dependencies { implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') { exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence' } + implementation('io.github.openfeign.querydsl:querydsl-core:6.2.1', + 'io.github.openfeign.querydsl:querydsl-jpa:6.2.1') + annotationProcessor( + 'io.github.openfeign.querydsl:querydsl-apt:6.2.1:jakarta', + 'jakarta.persistence:jakarta.persistence-api:3.1.0', + ) // testCompile dependencies are ONLY used in src/test, not src/main. // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! // diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index 38433c3ab41..83ab356a716 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -196,6 +196,13 @@ dependencies { implementation 'io.github.classgraph:classgraph' + implementation('io.github.openfeign.querydsl:querydsl-core:6.2.1', + 'io.github.openfeign.querydsl:querydsl-jpa:6.2.1') + annotationProcessor( + 'io.github.openfeign.querydsl:querydsl-apt:6.2.1:jakarta', + 'jakarta.persistence:jakarta.persistence-api:3.1.0', + ) + // testCompile dependencies are ONLY used in src/test, not src/main. // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! // diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsCustomRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsCustomRepository.java new file mode 100644 index 00000000000..0eb72086198 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsCustomRepository.java @@ -0,0 +1,31 @@ +/** + * 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.account.domain; + +import java.util.Collection; +import org.apache.fineract.portfolio.account.data.AccountAssociationsData; + +public interface AccountAssociationsCustomRepository { + + AccountAssociationsData retrieveLoanLinkedAssociation(Long loanId); + + AccountAssociationsData retrieveSavingsLinkedAssociation(Long savingsId); + + Collection retrieveLoanAssociations(Long loanId, Integer associationType); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsCustomRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsCustomRepositoryImpl.java new file mode 100644 index 00000000000..83578ec06f7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsCustomRepositoryImpl.java @@ -0,0 +1,114 @@ +/** + * 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.account.domain; + +import static java.util.stream.Collectors.toList; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.jpa.impl.JPAQuery; +import jakarta.persistence.EntityManager; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.portfolio.account.data.AccountAssociationsData; +import org.apache.fineract.portfolio.account.data.PortfolioAccountData; +import org.apache.fineract.portfolio.loanaccount.domain.QLoan; +import org.apache.fineract.portfolio.savings.domain.QSavingsAccount; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class AccountAssociationsCustomRepositoryImpl implements AccountAssociationsCustomRepository { + + private final EntityManager entityManager; + + @Override + public AccountAssociationsData retrieveLoanLinkedAssociation(final Long loanId) { + final QAccountAssociations qAccountAssociations = QAccountAssociations.accountAssociations; + final JPAQuery query = getAccountAssociationsSelectQuery(); + final Tuple queryResult = query + .where(eq(qAccountAssociations.loanAccount.id, loanId) + .and(qAccountAssociations.associationType.eq(AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()))) + .fetchOne(); + + return queryResult != null ? mapQueryResultToAccountAssociationsData(queryResult) : null; + } + + @Override + public AccountAssociationsData retrieveSavingsLinkedAssociation(final Long savingsId) { + final QAccountAssociations qAccountAssociations = QAccountAssociations.accountAssociations; + final JPAQuery query = getAccountAssociationsSelectQuery(); + final Tuple queryResult = query + .where(eq(qAccountAssociations.savingsAccount.id, savingsId) + .and(qAccountAssociations.associationType.eq(AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()))) + .fetchOne(); + + return queryResult != null ? mapQueryResultToAccountAssociationsData(queryResult) : null; + } + + @Override + public Collection retrieveLoanAssociations(final Long loanId, final Integer associationType) { + final QAccountAssociations qAccountAssociations = QAccountAssociations.accountAssociations; + final JPAQuery query = getAccountAssociationsSelectQuery(); + final List queryResult = query + .where(eq(qAccountAssociations.loanAccount.id, loanId).and(eq(qAccountAssociations.associationType, associationType))) + .fetch(); + return queryResult.isEmpty() ? Collections.emptyList() : mapQueryResultToAccountAssociationsDataList(queryResult); + } + + private JPAQuery getAccountAssociationsSelectQuery() { + final QAccountAssociations qAccountAssociations = QAccountAssociations.accountAssociations; + final QLoan qLoan = QLoan.loan; + final QSavingsAccount qSavingsAccount = QSavingsAccount.savingsAccount; + + final JPAQuery query = new JPAQuery<>(entityManager); + query.select(qAccountAssociations.id, qLoan.id.as("loanAccountId"), qLoan.accountNumber.as("loanAccountNo"), + qSavingsAccount.id.as("linkSavingsAccountId"), qSavingsAccount.accountNumber.as("linkSavingsAccountNo")) + .from(qAccountAssociations).leftJoin(qAccountAssociations.loanAccount, qLoan) + .on(qLoan.id.eq(qAccountAssociations.loanAccount.id)).leftJoin(qAccountAssociations.linkedSavingsAccount, qSavingsAccount) + .on(qSavingsAccount.id.eq(qAccountAssociations.linkedSavingsAccount.id)); + + return query; + } + + private AccountAssociationsData mapQueryResultToAccountAssociationsData(final Tuple queryResult) { + final QAccountAssociations qAccountAssociations = QAccountAssociations.accountAssociations; + final QLoan qLoan = QLoan.loan; + final QSavingsAccount qSavingsAccount = QSavingsAccount.savingsAccount; + + final PortfolioAccountData account = PortfolioAccountData.lookup(queryResult.get(qLoan.id.as("loanAccountId")), + queryResult.get(qLoan.accountNumber.as("loanAccountNo"))); + final PortfolioAccountData linkedAccount = PortfolioAccountData.lookup( + queryResult.get(qSavingsAccount.id.as("linkSavingsAccountId")), + queryResult.get(qSavingsAccount.accountNumber.as("linkSavingsAccountNo"))); + + return new AccountAssociationsData(queryResult.get(qAccountAssociations.id), account, linkedAccount); + } + + private List mapQueryResultToAccountAssociationsDataList(final List queryResult) { + return queryResult.stream().map(this::mapQueryResultToAccountAssociationsData).collect(toList()); + } + + private BooleanExpression eq(final SimpleExpression expression, final T value) { + return value == null ? expression.isNull() : expression.eq(value); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsRepository.java index 8c87a7e0c1a..d0996462a46 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountAssociationsRepository.java @@ -20,16 +20,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; public interface AccountAssociationsRepository - extends JpaRepository, JpaSpecificationExecutor { - - @Query("select aa from AccountAssociations aa where aa.loanAccount.id= :loanId and aa.associationType = :associationType") - AccountAssociations findByLoanIdAndType(@Param("loanId") Long loanId, @Param("associationType") Integer accountAssociationType); - - @Query("select aa from AccountAssociations aa where aa.savingsAccount.id= :savingsId and aa.associationType = :associationType") - AccountAssociations findBySavingsIdAndType(@Param("savingsId") Long savingsId, - @Param("associationType") Integer accountAssociationType); -} + extends JpaRepository, JpaSpecificationExecutor {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferRepository.java index 7e395b20311..1adc4a01d2d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferRepository.java @@ -18,25 +18,9 @@ */ package org.apache.fineract.portfolio.account.domain; -import java.util.Collection; -import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; -public interface AccountTransferRepository - extends JpaRepository, JpaSpecificationExecutor { - - @Query("select att from AccountTransferTransaction att where att.accountTransferDetails.fromLoanAccount.id= :accountNumber and att.reversed=false") - List findByFromLoanId(@Param("accountNumber") Long accountNumber); - - @Query("select att from AccountTransferTransaction att where (att.accountTransferDetails.fromLoanAccount.id= :accountNumber or att.accountTransferDetails.toLoanAccount.id=:accountNumber) and att.reversed=false order by att.id desc") - List findAllByLoanId(@Param("accountNumber") Long accountNumber); - - @Query("select att from AccountTransferTransaction att where att.toLoanTransaction.id= :loanTransactionId and att.reversed=false") - AccountTransferTransaction findByToLoanTransactionId(@Param("loanTransactionId") Long loanTransactionId); - - @Query("select att from AccountTransferTransaction att where att.fromLoanTransaction.id IN :loanTransactions and att.reversed=false") - List findByFromLoanTransactions(@Param("loanTransactions") Collection loanTransactions); -} +public interface AccountTransferRepository extends JpaRepository, + JpaSpecificationExecutor, QuerydslPredicateExecutor {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionCustomRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionCustomRepository.java new file mode 100644 index 00000000000..fb41d33d957 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionCustomRepository.java @@ -0,0 +1,26 @@ +/** + * 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.account.domain; + +import java.time.LocalDate; + +public interface AccountTransferStandingInstructionCustomRepository { + + void updateLastRunDateById(long id, LocalDate lastRunDate); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionCustomRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionCustomRepositoryImpl.java new file mode 100644 index 00000000000..4fe1562d2d6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionCustomRepositoryImpl.java @@ -0,0 +1,41 @@ +/** + * 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.account.domain; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import java.time.LocalDate; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class AccountTransferStandingInstructionCustomRepositoryImpl implements AccountTransferStandingInstructionCustomRepository { + + private final EntityManager entityManager; + + @Override + public void updateLastRunDateById(final long id, final LocalDate lastRunDate) { + final JPAQueryFactory queryFactory = new JPAQueryFactory(this.entityManager); + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + + queryFactory.update(qAccountTransferStandingInstruction).set(qAccountTransferStandingInstruction.latsRunDate, lastRunDate) + .where(qAccountTransferStandingInstruction.id.eq(id)).execute(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionsHistory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionsHistory.java new file mode 100644 index 00000000000..b2c1668e2f7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionsHistory.java @@ -0,0 +1,57 @@ +/** + * 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.account.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; + +@Entity +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "m_account_transfer_standing_instructions_history") +public class AccountTransferStandingInstructionsHistory extends AbstractPersistableCustom { + + @ManyToOne + @JoinColumn(name = "standing_instruction_id") + private AccountTransferStandingInstruction accountTransferStandingInstruction; + + @Column(name = "status", length = 20) + private String status; + + @Column(name = "execution_time") + private LocalDateTime executionTime; + + @Column(name = "amount", scale = 6, precision = 19) + private BigDecimal amount; + + @Column(name = "error_log", length = 500) + private String errorLog; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionsHistoryCustomRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionsHistoryCustomRepository.java new file mode 100644 index 00000000000..42700b07e6c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionsHistoryCustomRepository.java @@ -0,0 +1,26 @@ +/** + * 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.account.domain; + +import java.math.BigDecimal; + +public interface AccountTransferStandingInstructionsHistoryCustomRepository { + + void createNewHistory(long instructionId, BigDecimal transactionAmount, boolean transferCompleted, String errorLog); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionsHistoryCustomRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionsHistoryCustomRepositoryImpl.java new file mode 100644 index 00000000000..65eeff194b8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferStandingInstructionsHistoryCustomRepositoryImpl.java @@ -0,0 +1,48 @@ +/** + * 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.account.domain; + +import jakarta.persistence.EntityManager; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class AccountTransferStandingInstructionsHistoryCustomRepositoryImpl + implements AccountTransferStandingInstructionsHistoryCustomRepository { + + private final EntityManager entityManager; + private final StandingInstructionRepository standingInstructionRepository; + + @Override + public void createNewHistory(final long instructionId, final BigDecimal transactionAmount, final boolean transferCompleted, + final String errorLog) { + final AccountTransferStandingInstructionsHistory newHistory = new AccountTransferStandingInstructionsHistory(); + newHistory.setAccountTransferStandingInstruction(standingInstructionRepository.getReferenceById(instructionId)); + newHistory.setStatus(transferCompleted ? "success" : "failed"); + newHistory.setAmount(transactionAmount); + newHistory.setExecutionTime(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant())); + newHistory.setErrorLog(errorLog); + + entityManager.persist(newHistory); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionRepository.java index b229abc1d56..a14bb5f16f8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/StandingInstructionRepository.java @@ -18,33 +18,10 @@ */ package org.apache.fineract.portfolio.account.domain; -import java.util.Collection; -import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; public interface StandingInstructionRepository extends JpaRepository, JpaSpecificationExecutor { - String FIND_BY_LOAN_AND_STATUS_QUERY = "select accountTransferStandingInstruction " - + "from AccountTransferStandingInstruction accountTransferStandingInstruction " - + "where accountTransferStandingInstruction.status = :status " - + "and (accountTransferStandingInstruction.accountTransferDetails.toLoanAccount = :loan " - + "or accountTransferStandingInstruction.accountTransferDetails.fromLoanAccount = :loan)"; - - String FIND_BY_SAVINGS_AND_STATUS_QUERY = "select accountTransferStandingInstruction " - + "from AccountTransferStandingInstruction accountTransferStandingInstruction " - + "where accountTransferStandingInstruction.status = :status " - + "and (accountTransferStandingInstruction.accountTransferDetails.toSavingsAccount = :savingsAccount " - + "or accountTransferStandingInstruction.accountTransferDetails.fromSavingsAccount = :savingsAccount)"; - - @Query(FIND_BY_LOAN_AND_STATUS_QUERY) - Collection findByLoanAccountAndStatus(@Param("loan") Loan loan, @Param("status") Integer status); - - @Query(FIND_BY_SAVINGS_AND_STATUS_QUERY) - Collection findBySavingsAccountAndStatus(@Param("savingsAccount") SavingsAccount savingsAccount, - @Param("status") Integer status); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsConfig.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsConfig.java index 69fecbdb4c4..0484399f6a8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsConfig.java @@ -18,8 +18,9 @@ */ package org.apache.fineract.portfolio.account.jobs.executestandinginstructions; -import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstructionCustomRepositoryImpl; +import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstructionsHistoryCustomRepositoryImpl; import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService; import org.springframework.batch.core.Job; @@ -31,7 +32,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.PlatformTransactionManager; @Configuration @@ -44,11 +44,11 @@ public class ExecuteStandingInstructionsConfig { @Autowired private StandingInstructionReadPlatformService standingInstructionReadPlatformService; @Autowired - private JdbcTemplate jdbcTemplate; + private AccountTransfersWritePlatformService accountTransfersWritePlatformService; @Autowired - private DatabaseSpecificSQLGenerator sqlGenerator; + private AccountTransferStandingInstructionCustomRepositoryImpl accountTransferStandingInstructionCustomRepositoryImpl; @Autowired - private AccountTransfersWritePlatformService accountTransfersWritePlatformService; + private AccountTransferStandingInstructionsHistoryCustomRepositoryImpl accountTransferStandingInstructionsHistoryCustomRepositoryImpl; @Bean protected Step executeStandingInstructionsStep() { @@ -64,7 +64,7 @@ public Job executeStandingInstructionsJob() { @Bean public ExecuteStandingInstructionsTasklet executeStandingInstructionsTasklet() { - return new ExecuteStandingInstructionsTasklet(standingInstructionReadPlatformService, jdbcTemplate, sqlGenerator, - accountTransfersWritePlatformService); + return new ExecuteStandingInstructionsTasklet(standingInstructionReadPlatformService, accountTransfersWritePlatformService, + accountTransferStandingInstructionCustomRepositoryImpl, accountTransferStandingInstructionsHistoryCustomRepositoryImpl); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java index 5b5550f8106..852960ded9a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java @@ -29,12 +29,13 @@ import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.portfolio.account.data.AccountTransferDTO; import org.apache.fineract.portfolio.account.data.StandingInstructionData; import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData; import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType; +import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstructionCustomRepositoryImpl; +import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstructionsHistoryCustomRepositoryImpl; import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; import org.apache.fineract.portfolio.account.domain.StandingInstructionType; import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; @@ -48,19 +49,18 @@ import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; -import org.springframework.jdbc.core.JdbcTemplate; @Slf4j @RequiredArgsConstructor public class ExecuteStandingInstructionsTasklet implements Tasklet { private final StandingInstructionReadPlatformService standingInstructionReadPlatformService; - private final JdbcTemplate jdbcTemplate; - private final DatabaseSpecificSQLGenerator sqlGenerator; private final AccountTransfersWritePlatformService accountTransfersWritePlatformService; + private final AccountTransferStandingInstructionCustomRepositoryImpl accountTransferStandingInstructionCustomRepositoryImpl; + private final AccountTransferStandingInstructionsHistoryCustomRepositoryImpl accountTransferStandingInstructionsHistoryCustomRepositoryImpl; @Override - public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws JobExecutionException { Collection instructionData = standingInstructionReadPlatformService .retrieveAll(StandingInstructionStatus.ACTIVE.getValue()); List errors = new ArrayList<>(); @@ -113,8 +113,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId()); if (transferCompleted) { - final String updateQuery = "UPDATE m_account_transfer_standing_instructions SET last_run_date = ? where id = ?"; - jdbcTemplate.update(updateQuery, transactionDate, data.getId()); + accountTransferStandingInstructionCustomRepositoryImpl.updateLastRunDateById(data.getId(), transactionDate); } } @@ -127,10 +126,8 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon private boolean transferAmount(final List errors, final AccountTransferDTO accountTransferDTO, final Long instructionId) { boolean transferCompleted = true; - StringBuilder errorLog = new StringBuilder(); - StringBuilder updateQuery = new StringBuilder( - "INSERT INTO m_account_transfer_standing_instructions_history (standing_instruction_id, " + sqlGenerator.escape("status") - + ", amount,execution_time, error_log) VALUES ("); + final StringBuilder errorLog = new StringBuilder(); + try { accountTransfersWritePlatformService.transferFunds(accountTransferDTO); } catch (final PlatformApiDataValidationException e) { @@ -151,17 +148,14 @@ private boolean transferAmount(final List errors, final AccountTransf errorLog.append("Exception while trasfering funds ").append(e.getMessage()); } - updateQuery.append(instructionId).append(","); - if (errorLog.length() > 0) { + + if (!errorLog.isEmpty()) { transferCompleted = false; - updateQuery.append("'failed'").append(","); - } else { - updateQuery.append("'success'").append(","); } - updateQuery.append(accountTransferDTO.getTransactionAmount().doubleValue()); - updateQuery.append(", now(),"); - updateQuery.append("'").append(errorLog).append("')"); - jdbcTemplate.update(updateQuery.toString()); + + accountTransferStandingInstructionsHistoryCustomRepositoryImpl.createNewHistory(instructionId, + accountTransferDTO.getTransactionAmount(), transferCompleted, String.valueOf(errorLog)); + return transferCompleted; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java index f26eb598475..7ff353b73e0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java @@ -18,86 +18,75 @@ */ package org.apache.fineract.portfolio.account.service; -import java.sql.ResultSet; -import java.sql.SQLException; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.jpa.impl.JPAQuery; +import jakarta.persistence.EntityManager; import java.util.Collection; import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.portfolio.account.data.AccountAssociationsData; import org.apache.fineract.portfolio.account.data.PortfolioAccountData; import org.apache.fineract.portfolio.account.domain.AccountAssociationType; +import org.apache.fineract.portfolio.account.domain.AccountAssociationsCustomRepository; +import org.apache.fineract.portfolio.account.domain.QAccountAssociations; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; +import org.apache.fineract.portfolio.loanaccount.domain.QLoan; +import org.apache.fineract.portfolio.savings.domain.QSavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; @RequiredArgsConstructor @Slf4j public class AccountAssociationsReadPlatformServiceImpl implements AccountAssociationsReadPlatformService { - private final JdbcTemplate jdbcTemplate; + private final EntityManager entityManager; + private final AccountAssociationsCustomRepository accountAssociationsCustomRepository; @Override public PortfolioAccountData retriveLoanLinkedAssociation(final Long loanId) { - PortfolioAccountData linkedAccount = null; - final AccountAssociationsMapper mapper = new AccountAssociationsMapper(); - final String sql = "select " + mapper.schema() + " where aa.loan_account_id = ? and aa.association_type_enum = ?"; - try { - final AccountAssociationsData accountAssociationsData = this.jdbcTemplate.queryForObject(sql, mapper, loanId, // NOSONAR - AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()); - if (accountAssociationsData != null) { - linkedAccount = accountAssociationsData.linkedAccount(); - } - } catch (final EmptyResultDataAccessException e) { - log.debug("Linking account is not configured"); + final AccountAssociationsData accountAssociationsData = accountAssociationsCustomRepository.retrieveLoanLinkedAssociation(loanId); + if (accountAssociationsData != null) { + return accountAssociationsData.linkedAccount(); } - return linkedAccount; + log.debug("Linking account is not configured"); + return null; } @Override public Collection retriveLoanAssociations(final Long loanId, final Integer associationType) { - final AccountAssociationsMapper mapper = new AccountAssociationsMapper(); - final String sql = "select " + mapper.schema() + " where aa.loan_account_id = ? and aa.association_type_enum = ?"; - try { - return this.jdbcTemplate.query(sql, mapper, new Object[] { loanId, associationType }); // NOSONAR - } catch (final EmptyResultDataAccessException e) { - return null; - } - + return accountAssociationsCustomRepository.retrieveLoanAssociations(loanId, associationType); } @Override public PortfolioAccountData retriveSavingsLinkedAssociation(final Long savingsId) { - PortfolioAccountData linkedAccount = null; - final AccountAssociationsMapper mapper = new AccountAssociationsMapper(); - final String sql = "select " + mapper.schema() + " where aa.savings_account_id = ? and aa.association_type_enum = ?"; - try { - final AccountAssociationsData accountAssociationsData = this.jdbcTemplate.queryForObject(sql, mapper, // NOSONAR - new Object[] { savingsId, AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue() }); - if (accountAssociationsData != null) { - linkedAccount = accountAssociationsData.linkedAccount(); - } - } catch (final EmptyResultDataAccessException e) { - log.debug("Linking account is not configured"); + final AccountAssociationsData accountAssociationsData = accountAssociationsCustomRepository + .retrieveSavingsLinkedAssociation(savingsId); + if (accountAssociationsData != null) { + return accountAssociationsData.linkedAccount(); } - return linkedAccount; + log.debug("Linking account is not configured"); + return null; } @Override public boolean isLinkedWithAnyActiveAccount(final Long savingsId) { boolean hasActiveAccount = false; - final String sql1 = "select aa.is_active as active,aa.association_type_enum as type, loanAccount.loan_status_id as loanStatus," - + "savingAccount.status_enum as savingsStatus from m_portfolio_account_associations aa " - + "left join m_loan loanAccount on loanAccount.id = aa.loan_account_id " - + "left join m_savings_account savingAccount on savingAccount.id = aa.savings_account_id " - + "where aa.linked_savings_account_id = ?"; + final QAccountAssociations qAccountAssociations = QAccountAssociations.accountAssociations; + final QLoan qLoan = QLoan.loan; + final QSavingsAccount qSavingsAccount = QSavingsAccount.savingsAccount; + + final JPAQuery> query = new JPAQuery<>(entityManager); + query.select(qAccountAssociations.active, qAccountAssociations.associationType, qLoan.loanStatus, + qSavingsAccount.status.as("savingsStatus")).from(qAccountAssociations).leftJoin(qAccountAssociations.loanAccount, qLoan) + .on(qLoan.id.eq(qAccountAssociations.loanAccount.id)).leftJoin(qAccountAssociations.savingsAccount, qSavingsAccount) + .on(qSavingsAccount.id.eq(qAccountAssociations.savingsAccount.id)) + .where(eq(qAccountAssociations.linkedSavingsAccount.id, savingsId)); + + List> statusList = query.fetch(); - final List> statusList = this.jdbcTemplate.queryForList(sql1, savingsId); for (final Map statusMap : statusList) { AccountAssociationType associationType = AccountAssociationType.fromInt((Integer) statusMap.get("type")); if (!associationType.isLinkedAccountAssociation() && (Boolean) statusMap.get("active")) { @@ -125,69 +114,16 @@ public boolean isLinkedWithAnyActiveAccount(final Long savingsId) { return hasActiveAccount; } - private static final class AccountAssociationsMapper implements RowMapper { - - private final String schemaSql; - - AccountAssociationsMapper() { - final StringBuilder sqlBuilder = new StringBuilder(); - sqlBuilder.append("aa.id as id,"); - // sqlBuilder.append("savingsAccount.id as savingsAccountId, - // savingsAccount.account_no as savingsAccountNo,"); - sqlBuilder.append("loanAccount.id as loanAccountId, loanAccount.account_no as loanAccountNo,"); - // sqlBuilder.append("linkLoanAccount.id as linkLoanAccountId, - // linkLoanAccount.account_no as linkLoanAccountNo, "); - sqlBuilder.append("linkSavingsAccount.id as linkSavingsAccountId, linkSavingsAccount.account_no as linkSavingsAccountNo "); - sqlBuilder.append("from m_portfolio_account_associations aa "); - // sqlBuilder.append("left join m_savings_account savingsAccount on - // savingsAccount.id = aa.savings_account_id "); - sqlBuilder.append("left join m_loan loanAccount on loanAccount.id = aa.loan_account_id "); - sqlBuilder.append("left join m_savings_account linkSavingsAccount on linkSavingsAccount.id = aa.linked_savings_account_id "); - // sqlBuilder.append("left join m_loan linkLoanAccount on - // linkLoanAccount.id = aa.linked_loan_account_id "); - this.schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public AccountAssociationsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong("id"); - // final Long savingsAccountId = JdbcSupport.getLong(rs, - // "savingsAccountId"); - // final String savingsAccountNo = rs.getString("savingsAccountNo"); - final Long loanAccountId = JdbcSupport.getLong(rs, "loanAccountId"); - final String loanAccountNo = rs.getString("loanAccountNo"); - final PortfolioAccountData account = PortfolioAccountData.lookup(loanAccountId, loanAccountNo); - /* - * if (savingsAccountId != null) { account = PortfolioAccountData.lookup(savingsAccountId, - * savingsAccountNo); } else if (loanAccountId != null) { account = - * PortfolioAccountData.lookup(loanAccountId, loanAccountNo); } - */ - final Long linkSavingsAccountId = JdbcSupport.getLong(rs, "linkSavingsAccountId"); - final String linkSavingsAccountNo = rs.getString("linkSavingsAccountNo"); - // final Long linkLoanAccountId = JdbcSupport.getLong(rs, - // "linkLoanAccountId"); - // final String linkLoanAccountNo = - // rs.getString("linkLoanAccountNo"); - final PortfolioAccountData linkedAccount = PortfolioAccountData.lookup(linkSavingsAccountId, linkSavingsAccountNo); - /* - * if (linkSavingsAccountId != null) { linkedAccount = PortfolioAccountData.lookup(linkSavingsAccountId, - * linkSavingsAccountNo); } else if (linkLoanAccountId != null) { linkedAccount = - * PortfolioAccountData.lookup(linkLoanAccountId, linkLoanAccountNo); } - */ - - return new AccountAssociationsData(id, account, linkedAccount); - } - - } - @Override public PortfolioAccountData retriveSavingsAccount(final Long savingsId) { - String accountNo = jdbcTemplate.queryForObject("select account_no from m_savings_account where id = ?", String.class, savingsId); + final QSavingsAccount qSavingsAccount = QSavingsAccount.savingsAccount; + final JPAQuery query = new JPAQuery<>(entityManager); + final String accountNo = query.select(qSavingsAccount.accountNumber).from(qSavingsAccount).where(eq(qSavingsAccount.id, savingsId)) + .fetchOne(); return PortfolioAccountData.lookup(savingsId, accountNo); } + + private BooleanExpression eq(final SimpleExpression expression, final T value) { + return value == null ? expression.isNull() : expression.eq(value); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java index 619da29815b..056a1731d7d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java @@ -18,65 +18,64 @@ */ package org.apache.fineract.portfolio.account.service; +import static com.querydsl.core.types.dsl.Expressions.ONE; + +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; import java.math.BigDecimal; -import java.sql.ResultSet; -import java.sql.SQLException; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import lombok.AllArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; -import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.PaginationHelper; import org.apache.fineract.infrastructure.core.service.SearchParameters; -import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; -import org.apache.fineract.infrastructure.security.utils.ColumnValidator; -import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.monetary.domain.QApplicationCurrency; import org.apache.fineract.organisation.office.data.OfficeData; +import org.apache.fineract.organisation.office.domain.QOffice; import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.data.AccountTransferData; import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO; import org.apache.fineract.portfolio.account.data.PortfolioAccountData; import org.apache.fineract.portfolio.account.domain.AccountTransferType; +import org.apache.fineract.portfolio.account.domain.QAccountTransferDetails; +import org.apache.fineract.portfolio.account.domain.QAccountTransferStandingInstruction; +import org.apache.fineract.portfolio.account.domain.QAccountTransferTransaction; import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException; import org.apache.fineract.portfolio.client.data.ClientData; +import org.apache.fineract.portfolio.client.domain.QClient; import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; +import org.apache.fineract.portfolio.loanaccount.domain.QLoan; +import org.apache.fineract.portfolio.loanaccount.domain.QLoanTransaction; +import org.apache.fineract.portfolio.savings.domain.QSavingsAccount; +import org.apache.fineract.portfolio.savings.domain.QSavingsAccountTransaction; import org.springframework.util.CollectionUtils; +@AllArgsConstructor public class AccountTransfersReadPlatformServiceImpl implements AccountTransfersReadPlatformService { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - private final JdbcTemplate jdbcTemplate; private final ClientReadPlatformService clientReadPlatformService; private final OfficeReadPlatformService officeReadPlatformService; private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService; - private final ColumnValidator columnValidator; - private final DatabaseSpecificSQLGenerator sqlGenerator; - // mapper - private final AccountTransfersMapper accountTransfersMapper; // pagination private final PaginationHelper paginationHelper; - - public AccountTransfersReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, - final ClientReadPlatformService clientReadPlatformService, final OfficeReadPlatformService officeReadPlatformService, - final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService, final ColumnValidator columnValidator, - DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) { - this.jdbcTemplate = jdbcTemplate; - this.clientReadPlatformService = clientReadPlatformService; - this.officeReadPlatformService = officeReadPlatformService; - this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformService; - this.columnValidator = columnValidator; - this.sqlGenerator = sqlGenerator; - this.accountTransfersMapper = new AccountTransfersMapper(); - this.paginationHelper = paginationHelper; - } + private final EntityManager entityManager; @Override public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId, @@ -86,19 +85,17 @@ public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN); final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS); - final Integer mostRelevantFromAccountType = fromAccountType; final Collection fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType); final Collection toAccountTypeOptions; - if (mostRelevantFromAccountType != null && mostRelevantFromAccountType == 1) { + if (fromAccountType != null && fromAccountType == 1) { // overpaid loan amt transfer to savings account - toAccountTypeOptions = Arrays.asList(savingsAccountType); + toAccountTypeOptions = Collections.singletonList(savingsAccountType); } else { toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType); } - final Integer mostRelevantToAccountType = toAccountType; - final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType); - final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType); + final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(fromAccountType); + final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(toAccountType); // from settings OfficeData fromOffice = null; @@ -121,7 +118,7 @@ public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long if (fromAccountId != null) { Integer accountType; - if (mostRelevantFromAccountType == 1) { + if (fromAccountType == 1) { accountType = PortfolioAccountType.LOAN.getValue(); } else { accountType = PortfolioAccountType.SAVINGS.getValue(); @@ -136,11 +133,10 @@ public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId); mostRelevantFromOfficeId = fromClient.getOfficeId(); long[] loanStatus = null; - if (mostRelevantFromAccountType == 1) { + if (fromAccountType == 1) { loanStatus = new long[] { 300, 700 }; } - PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(mostRelevantFromAccountType, mostRelevantFromClientId, - loanStatus); + PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(fromAccountType, mostRelevantFromClientId, loanStatus); fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO); } @@ -158,8 +154,7 @@ public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long Collection toClientOptions = null; if (toAccountId != null && fromAccount != null) { - toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType, - fromAccount.getCurrencyCode()); + toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, toAccountType, fromAccount.getCurrencyCode()); mostRelevantToClientId = toAccount.getClientId(); } @@ -169,7 +164,7 @@ public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); - toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); + toAccountOptions = retrieveToAccounts(fromAccount, toAccountType, mostRelevantToClientId); } if (mostRelevantToOfficeId != null) { @@ -180,7 +175,7 @@ public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long if (toClientOptions != null && toClientOptions.size() == 1) { toClient = new ArrayList<>(toClientOptions).get(0); - toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); + toAccountOptions = retrieveToAccounts(fromAccount, toAccountType, mostRelevantToClientId); } } @@ -208,106 +203,129 @@ private Collection retrieveToAccounts(final PortfolioAccou @Override public Page retrieveAll(final SearchParameters searchParameters, final Long accountDetailId) { + final JPAQuery query = getAccountTransferSelectQuery(); + final JPAQuery totalCountQuery = getAccountTransferCountQuery(); - final StringBuilder sqlBuilder = new StringBuilder(200); - sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); - sqlBuilder.append(this.accountTransfersMapper.schema()); - Object[] finalObjectArray = {}; if (accountDetailId != null) { - sqlBuilder.append(" where att.account_transfer_details_id=?"); - finalObjectArray = new Object[] { accountDetailId }; + query.where(QAccountTransferTransaction.accountTransferTransaction.accountTransferDetails.id.eq(accountDetailId)); + totalCountQuery.where(QAccountTransferTransaction.accountTransferTransaction.accountTransferDetails.id.eq(accountDetailId)); } if (searchParameters.hasOrderBy()) { - sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); - this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); - if (searchParameters.hasSortOrder()) { - sqlBuilder.append(' ').append(searchParameters.getSortOrder()); - this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); + final Order order = searchParameters.getSortOrder().equalsIgnoreCase("desc") ? Order.DESC + : searchParameters.getSortOrder().equalsIgnoreCase("asc") || searchParameters.getSortOrder().isEmpty() ? Order.ASC + : null; + if (order == null) { + throw new IllegalArgumentException("Unknown sort order: " + searchParameters.getSortOrder()); } + + final OrderSpecifier specifier = OrderSpecifierFactory.getSpecifier(searchParameters.getOrderBy(), order); + query.orderBy(specifier); } if (searchParameters.hasLimit()) { - sqlBuilder.append(" limit ").append(searchParameters.getLimit()); + query.limit(searchParameters.getLimit()); if (searchParameters.hasOffset()) { - sqlBuilder.append(" offset ").append(searchParameters.getOffset()); + query.offset(searchParameters.getOffset()); } } - return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), finalObjectArray, this.accountTransfersMapper); + return this.paginationHelper.createPageFromItems(query.fetch(), Objects.requireNonNull(totalCountQuery.fetchOne())); } @Override public AccountTransferData retrieveOne(final Long transferId) { - - try { - final String sql = "select " + this.accountTransfersMapper.schema() + " where att.id = ?"; - - return this.jdbcTemplate.queryForObject(sql, this.accountTransfersMapper, new Object[] { transferId }); // NOSONAR - } catch (final EmptyResultDataAccessException e) { - throw new AccountTransferNotFoundException(transferId, e); - } + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final JPAQuery query = getAccountTransferSelectQuery().where(eq(qAccountTransferTransaction.id, transferId)); + return Optional.ofNullable(query.fetchOne()).orElseThrow(() -> new AccountTransferNotFoundException(transferId)); } @Override public Collection fetchPostInterestTransactionIds(final Long accountId) { - final String sql = "select att.from_savings_transaction_id from m_account_transfer_transaction att inner join m_account_transfer_details atd on atd.id = att.account_transfer_details_id where atd.from_savings_account_id=? and att.is_reversed = false and atd.transfer_type = ?"; - - return this.jdbcTemplate.queryForList(sql, Long.class, accountId, AccountTransferType.INTEREST_TRANSFER.getValue()); + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final QAccountTransferDetails qAccountTransferDetails = QAccountTransferDetails.accountTransferDetails; + + final JPAQuery query = new JPAQuery<>(entityManager); + query.select(qAccountTransferTransaction.fromSavingsTransaction.id).from(qAccountTransferTransaction) + .innerJoin(qAccountTransferTransaction.accountTransferDetails, qAccountTransferDetails) + .on(qAccountTransferDetails.id.eq(qAccountTransferTransaction.accountTransferDetails.id)) + .where(qAccountTransferTransaction.reversed.eq(false) + .and(qAccountTransferDetails.transferType.eq(AccountTransferType.INTEREST_TRANSFER.getValue())) + .and(eq(qAccountTransferDetails.fromSavingsAccount.id, accountId))); + + return query.fetch(); } @Override public Collection fetchPostInterestTransactionIdsWithPivotDate(final Long accountId, final LocalDate pivotDate) { - final String sql = "select att.from_savings_transaction_id from m_account_transfer_transaction att inner join m_account_transfer_details atd on atd.id = att.account_transfer_details_id where atd.from_savings_account_id=? and att.is_reversed = false and atd.transfer_type = ? and att.transaction_date >= ?"; - - return this.jdbcTemplate.queryForList(sql, Long.class, accountId, AccountTransferType.INTEREST_TRANSFER.getValue(), pivotDate); + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final QAccountTransferDetails qAccountTransferDetails = QAccountTransferDetails.accountTransferDetails; + + final JPAQuery query = new JPAQuery<>(entityManager); + + return query.select(qAccountTransferTransaction.fromSavingsTransaction.id).from(qAccountTransferTransaction) + .innerJoin(qAccountTransferTransaction.accountTransferDetails, qAccountTransferDetails) + .on(qAccountTransferDetails.id.eq(qAccountTransferTransaction.accountTransferDetails.id)) + .where(eq(qAccountTransferDetails.fromSavingsAccount.id, accountId).and(qAccountTransferTransaction.reversed.eq(false)) + .and(qAccountTransferDetails.transferType.eq(AccountTransferType.INTEREST_TRANSFER.getValue())) + .and(qAccountTransferTransaction.date.after(pivotDate.minusDays(1)))) + .fetch(); } @Override public boolean isAccountTransfer(final Long transactionId, final PortfolioAccountType accountType) { - final StringBuilder sql = new StringBuilder("select count(*) from m_account_transfer_transaction at where "); + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); + + final BooleanExpression predicate; if (accountType.isLoanAccount()) { - sql.append("at.from_loan_transaction_id=").append(transactionId).append(" or at.to_loan_transaction_id=").append(transactionId); + predicate = eq(qAccountTransferTransaction.fromLoanTransaction.id, transactionId) + .or(eq(qAccountTransferTransaction.toLoanTransaction.id, transactionId)); } else { - sql.append("at.from_savings_transaction_id=").append(transactionId).append(" or at.to_savings_transaction_id=") - .append(transactionId); + predicate = eq(qAccountTransferTransaction.fromSavingsTransaction.id, transactionId) + .or(eq(qAccountTransferTransaction.toSavingsTransaction.id, transactionId)); } - final int count = this.jdbcTemplate.queryForObject(sql.toString(), Integer.class); - return count > 0; + final Long count = queryFactory.select(qAccountTransferTransaction.count()).from(qAccountTransferTransaction).where(predicate) + .fetchOne(); + + return count != null && count > 0; } @Override public Page retrieveByStandingInstruction(final Long id, final SearchParameters searchParameters) { - - final StringBuilder sqlBuilder = new StringBuilder(200); - sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); - sqlBuilder.append(this.accountTransfersMapper.schema()).append( - " join m_account_transfer_standing_instructions atsi on atsi.account_transfer_details_id = att.account_transfer_details_id "); - sqlBuilder.append(" where atsi.id = ?"); + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final JPAQuery query = getAccountTransferSelectQuery().join(qAccountTransferStandingInstruction) + .on(qAccountTransferStandingInstruction.accountTransferDetails.id + .eq(QAccountTransferTransaction.accountTransferTransaction.accountTransferDetails.id)) + .where(eq(qAccountTransferStandingInstruction.id, id)); + final JPAQuery totalCountQuery = getAccountTransferCountQuery().join(qAccountTransferStandingInstruction) + .on(qAccountTransferStandingInstruction.accountTransferDetails.id + .eq(QAccountTransferTransaction.accountTransferTransaction.accountTransferDetails.id)) + .where(eq(qAccountTransferStandingInstruction.id, id)); if (searchParameters != null) { if (searchParameters.hasOrderBy()) { - sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); - this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); - if (searchParameters.hasSortOrder()) { - sqlBuilder.append(' ').append(searchParameters.getSortOrder()); - this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); + final Order order = searchParameters.getSortOrder().equalsIgnoreCase("desc") ? Order.DESC + : searchParameters.getSortOrder().equalsIgnoreCase("asc") || searchParameters.getSortOrder().isEmpty() ? Order.ASC + : null; + if (order == null) { + throw new IllegalArgumentException("Unknown sort order: " + searchParameters.getSortOrder()); } + + final OrderSpecifier specifier = OrderSpecifierFactory.getSpecifier(searchParameters.getOrderBy(), order); + query.orderBy(specifier); } if (searchParameters.hasLimit()) { - sqlBuilder.append(" "); + query.limit(searchParameters.getLimit()); if (searchParameters.hasOffset()) { - sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); - } else { - sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); + query.offset(searchParameters.getOffset()); } } } - final Object[] finalObjectArray = { id }; - return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), finalObjectArray, this.accountTransfersMapper); + return this.paginationHelper.createPageFromItems(query.fetch(), Objects.requireNonNull(totalCountQuery.fetchOne())); } @Override @@ -318,19 +336,17 @@ public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOffic final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN); final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS); - final Integer mostRelevantFromAccountType = fromAccountType; final Collection fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType); final Collection toAccountTypeOptions; - if (mostRelevantFromAccountType == 1) { + if (fromAccountType == 1) { // overpaid loan amt transfer to savings account - toAccountTypeOptions = Arrays.asList(savingsAccountType); + toAccountTypeOptions = Collections.singletonList(savingsAccountType); } else { toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType); } - final Integer mostRelevantToAccountType = toAccountType; - final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType); - final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType); + final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(fromAccountType); + final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(toAccountType); // from settings OfficeData fromOffice = null; @@ -353,7 +369,7 @@ public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOffic if (fromAccountId != null) { Integer accountType; - if (mostRelevantFromAccountType == 1) { + if (fromAccountType == 1) { accountType = PortfolioAccountType.LOAN.getValue(); } else { accountType = PortfolioAccountType.SAVINGS.getValue(); @@ -368,11 +384,10 @@ public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOffic fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId); mostRelevantFromOfficeId = fromClient.getOfficeId(); long[] loanStatus = null; - if (mostRelevantFromAccountType == 1) { + if (fromAccountType == 1) { loanStatus = new long[] { 300, 700 }; } - PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(mostRelevantFromAccountType, mostRelevantFromClientId, - loanStatus); + PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(fromAccountType, mostRelevantFromClientId, loanStatus); fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO); } @@ -390,8 +405,7 @@ public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOffic Collection toClientOptions = null; if (toAccountId != null && fromAccount != null) { - toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType, - fromAccount.getCurrencyCode()); + toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, toAccountType, fromAccount.getCurrencyCode()); mostRelevantToClientId = toAccount.getClientId(); } @@ -401,7 +415,7 @@ public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOffic toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); - toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); + toAccountOptions = retrieveToAccounts(fromAccount, toAccountType, mostRelevantToClientId); } if (mostRelevantToOfficeId != null) { @@ -412,7 +426,7 @@ public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOffic if (toClientOptions != null && toClientOptions.size() == 1) { toClient = new ArrayList<>(toClientOptions).get(0); - toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); + toAccountOptions = retrieveToAccounts(fromAccount, toAccountType, mostRelevantToClientId); } } @@ -423,133 +437,184 @@ public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOffic @Override public BigDecimal getTotalTransactionAmount(Long accountId, Integer accountType, LocalDate transactionDate) { - StringBuilder sqlBuilder = new StringBuilder(" select sum(trans.amount) as totalTransactionAmount "); - sqlBuilder.append(" from m_account_transfer_details as det "); - sqlBuilder.append(" inner join m_account_transfer_transaction as trans "); - sqlBuilder.append(" on det.id = trans.account_transfer_details_id "); - sqlBuilder.append(" where trans.is_reversed = false "); - sqlBuilder.append(" and trans.transaction_date = ? "); - sqlBuilder.append(" and IF(1=?, det.from_loan_account_id = ?, det.from_savings_account_id = ?) "); - - return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), BigDecimal.class, DATE_TIME_FORMATTER.format(transactionDate), - accountType, accountId, accountId); + final QAccountTransferDetails qAccountTransferDetails = QAccountTransferDetails.accountTransferDetails; + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + + final JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); + + BooleanExpression condition = qAccountTransferTransaction.reversed.eq(false) + .and(qAccountTransferTransaction.date.eq(LocalDate.parse(DATE_TIME_FORMATTER.format(transactionDate)))); + + if (PortfolioAccountType.LOAN.getValue().equals(accountType)) { + condition = condition.and(eq(qAccountTransferDetails.fromLoanAccount.id, accountId)); + } else { + condition = condition.and(eq(qAccountTransferDetails.fromSavingsAccount.id, accountId)); + } + + final BigDecimal totalTransactionAmount = queryFactory.select(qAccountTransferTransaction.amount.sumBigDecimal()) + .from(qAccountTransferDetails).innerJoin(qAccountTransferTransaction) + .on(qAccountTransferDetails.id.eq(qAccountTransferTransaction.accountTransferDetails.id)).where(condition).fetchOne(); + + return totalTransactionAmount != null ? totalTransactionAmount : BigDecimal.ZERO; } - private static final class AccountTransfersMapper implements RowMapper { - - private final String schemaSql; - - AccountTransfersMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("att.id as id, att.is_reversed as isReversed,"); - sqlBuilder.append("att.transaction_date as transferDate, att.amount as transferAmount,"); - sqlBuilder.append("att.description as transferDescription,"); - sqlBuilder.append("att.currency_code as currencyCode, att.currency_digits as currencyDigits,"); - sqlBuilder.append("att.currency_multiplesof as inMultiplesOf, "); - sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); - sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,"); - sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,"); - sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,"); - sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,"); - sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,"); - sqlBuilder.append("fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo,"); - sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,"); - sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo,"); - sqlBuilder.append("fromsavtran.id as fromSavingsAccountTransactionId,"); - sqlBuilder.append("fromsavtran.transaction_type_enum as fromSavingsAccountTransactionType,"); - sqlBuilder.append("tosavtran.id as toSavingsAccountTransactionId,"); - sqlBuilder.append("tosavtran.transaction_type_enum as toSavingsAccountTransactionType"); - sqlBuilder.append(" FROM m_account_transfer_transaction att "); - sqlBuilder.append("left join m_account_transfer_details atd on atd.id = att.account_transfer_details_id "); - sqlBuilder.append("join m_currency curr on curr.code = att.currency_code "); - sqlBuilder.append("join m_office fromoff on fromoff.id = atd.from_office_id "); - sqlBuilder.append("join m_office tooff on tooff.id = atd.to_office_id "); - sqlBuilder.append("join m_client fromclient on fromclient.id = atd.from_client_id "); - sqlBuilder.append("join m_client toclient on toclient.id = atd.to_client_id "); - sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id "); - sqlBuilder.append("left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id "); - sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id "); - sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id "); - sqlBuilder.append("left join m_savings_account_transaction fromsavtran on fromsavtran.id = att.from_savings_transaction_id "); - sqlBuilder.append("left join m_savings_account_transaction tosavtran on tosavtran.id = att.to_savings_transaction_id "); - sqlBuilder.append("left join m_loan_transaction fromloantran on fromloantran.id = att.from_savings_transaction_id "); - sqlBuilder.append("left join m_loan_transaction toloantran on toloantran.id = att.to_savings_transaction_id "); - - this.schemaSql = sqlBuilder.toString(); + private static final class OrderSpecifierFactory { + + @FunctionalInterface + interface OrderSpecifierGenerator { + + OrderSpecifier generate(Order order); } - public String schema() { - return this.schemaSql; + private static final Map fieldToSpecifier = new HashMap<>(); + + static { + fieldToSpecifier.put("id", order -> new OrderSpecifier<>(order, QAccountTransferTransaction.accountTransferTransaction.id)); + fieldToSpecifier.put("isReversed", + order -> new OrderSpecifier<>(order, QAccountTransferTransaction.accountTransferTransaction.reversed)); + fieldToSpecifier.put("transferDate", + order -> new OrderSpecifier<>(order, QAccountTransferTransaction.accountTransferTransaction.date)); + fieldToSpecifier.put("transferAmount", + order -> new OrderSpecifier<>(order, QAccountTransferTransaction.accountTransferTransaction.amount)); + fieldToSpecifier.put("transferDescription", + order -> new OrderSpecifier<>(order, QAccountTransferTransaction.accountTransferTransaction.description)); + fieldToSpecifier.put("currencyCode", + order -> new OrderSpecifier<>(order, QAccountTransferTransaction.accountTransferTransaction.currency.code)); + fieldToSpecifier.put("currencyDigits", order -> new OrderSpecifier<>(order, + QAccountTransferTransaction.accountTransferTransaction.currency.digitsAfterDecimal)); + fieldToSpecifier.put("inMultiplesOf", + order -> new OrderSpecifier<>(order, QAccountTransferTransaction.accountTransferTransaction.currency.inMultiplesOf)); + fieldToSpecifier.put("currencyName", order -> new OrderSpecifier<>(order, QApplicationCurrency.applicationCurrency.name)); + fieldToSpecifier.put("currencyNameCode", + order -> new OrderSpecifier<>(order, QApplicationCurrency.applicationCurrency.nameCode)); + fieldToSpecifier.put("currencyDisplaySymbol", + order -> new OrderSpecifier<>(order, QApplicationCurrency.applicationCurrency.displaySymbol)); + fieldToSpecifier.put("fromOfficeId", order -> new OrderSpecifier<>(order, new QOffice("fromOffice").id)); + fieldToSpecifier.put("fromOfficeName", order -> new OrderSpecifier<>(order, new QOffice("fromOffice").name)); + fieldToSpecifier.put("toOfficeId", order -> new OrderSpecifier<>(order, new QOffice("toOffice").id)); + fieldToSpecifier.put("toOfficeName", order -> new OrderSpecifier<>(order, new QOffice("toOffice").name)); + fieldToSpecifier.put("fromClientId", order -> new OrderSpecifier<>(order, new QClient("fromClient").id)); + fieldToSpecifier.put("fromClientName", order -> new OrderSpecifier<>(order, new QClient("fromClient").displayName)); + fieldToSpecifier.put("toClientId", order -> new OrderSpecifier<>(order, new QClient("toClient").id)); + fieldToSpecifier.put("toClientName", order -> new OrderSpecifier<>(order, new QClient("toClient").displayName)); + fieldToSpecifier.put("fromSavingsAccountId", order -> new OrderSpecifier<>(order, new QSavingsAccount("fromSavings").id)); + fieldToSpecifier.put("fromSavingsAccountNo", + order -> new OrderSpecifier<>(order, new QSavingsAccount("fromSavings").accountNumber)); + fieldToSpecifier.put("toSavingsAccountId", order -> new OrderSpecifier<>(order, new QSavingsAccount("toSavings").id)); + fieldToSpecifier.put("toSavingsAccountNo", + order -> new OrderSpecifier<>(order, new QSavingsAccount("toSavings").accountNumber)); + fieldToSpecifier.put("fromLoanAccountId", order -> new OrderSpecifier<>(order, new QLoan("fromLoan").id)); + fieldToSpecifier.put("fromLoanAccountNo", order -> new OrderSpecifier<>(order, new QLoan("fromLoan").accountNumber)); + fieldToSpecifier.put("toLoanAccountId", order -> new OrderSpecifier<>(order, new QLoan("toLoan").id)); + fieldToSpecifier.put("toLoanAccountNo", order -> new OrderSpecifier<>(order, new QLoan("toLoan").accountNumber)); + fieldToSpecifier.put("fromSavingsAccountTransactionId", + order -> new OrderSpecifier<>(order, new QSavingsAccountTransaction("fromSavingsTrans").id)); + fieldToSpecifier.put("fromSavingsAccountTransactionType", + order -> new OrderSpecifier<>(order, new QSavingsAccountTransaction("fromSavingsTrans").typeOf)); + fieldToSpecifier.put("toSavingsAccountTransactionId", + order -> new OrderSpecifier<>(order, new QSavingsAccountTransaction("toSavingsTrans").id)); + fieldToSpecifier.put("toSavingsAccountTransactionType", + order -> new OrderSpecifier<>(order, new QSavingsAccountTransaction("toSavingsTrans").typeOf)); } - @Override - public AccountTransferData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong("id"); - final boolean reversed = rs.getBoolean("isReversed"); - - final LocalDate transferDate = JdbcSupport.getLocalDate(rs, "transferDate"); - final BigDecimal transferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transferAmount"); - final String transferDescription = rs.getString("transferDescription"); - - final String currencyCode = rs.getString("currencyCode"); - final String currencyName = rs.getString("currencyName"); - final String currencyNameCode = rs.getString("currencyNameCode"); - final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, - currencyNameCode); - - final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId"); - final String fromOfficeName = rs.getString("fromOfficeName"); - final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null); - - final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId"); - final String toOfficeName = rs.getString("toOfficeName"); - final OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null); - - final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId"); - final String fromClientName = rs.getString("fromClientName"); - final ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName); - - final Long toClientId = JdbcSupport.getLong(rs, "toClientId"); - final String toClientName = rs.getString("toClientName"); - final ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName); - - final Long fromSavingsAccountId = JdbcSupport.getLong(rs, "fromSavingsAccountId"); - final String fromSavingsAccountNo = rs.getString("fromSavingsAccountNo"); - final Long fromLoanAccountId = JdbcSupport.getLong(rs, "fromLoanAccountId"); - final String fromLoanAccountNo = rs.getString("fromLoanAccountNo"); - PortfolioAccountData fromAccount = null; - EnumOptionData fromAccountType = null; - if (fromSavingsAccountId != null) { - fromAccount = PortfolioAccountData.lookup(fromSavingsAccountId, fromSavingsAccountNo); - fromAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS); - } else if (fromLoanAccountId != null) { - fromAccount = PortfolioAccountData.lookup(fromLoanAccountId, fromLoanAccountNo); - fromAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN); + public static OrderSpecifier getSpecifier(final String fieldName, final Order order) { + final OrderSpecifierGenerator generator = fieldToSpecifier.get(fieldName); + if (generator != null) { + return generator.generate(order); + } else { + throw new IllegalArgumentException("Unknown order field name: " + fieldName); } + } + } - PortfolioAccountData toAccount = null; - EnumOptionData toAccountType = null; - final Long toSavingsAccountId = JdbcSupport.getLong(rs, "toSavingsAccountId"); - final String toSavingsAccountNo = rs.getString("toSavingsAccountNo"); - final Long toLoanAccountId = JdbcSupport.getLong(rs, "toLoanAccountId"); - final String toLoanAccountNo = rs.getString("toLoanAccountNo"); - - if (toSavingsAccountId != null) { - toAccount = PortfolioAccountData.lookup(toSavingsAccountId, toSavingsAccountNo); - toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS); - } else if (toLoanAccountId != null) { - toAccount = PortfolioAccountData.lookup(toLoanAccountId, toLoanAccountNo); - toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN); - } + private JPAQuery getAccountTransferSelectQuery() { + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final QApplicationCurrency qApplicationCurrency = QApplicationCurrency.applicationCurrency; + final QOffice qFromOffice = new QOffice("fromOffice"); + final QOffice qToOffice = new QOffice("toOffice"); + final QClient qFromClient = new QClient("fromClient"); + final QClient qToClient = new QClient("toClient"); + final QSavingsAccount qFromSavingsAccount = new QSavingsAccount("fromSavings"); + final QSavingsAccount qToSavingsAccount = new QSavingsAccount("toSavings"); + final QLoan qFromLoan = new QLoan("fromLoan"); + final QLoan qToLoan = new QLoan("toLoan"); + final QSavingsAccountTransaction qFromSavingsAccountTransaction = new QSavingsAccountTransaction("fromSavingsTrans"); + final QSavingsAccountTransaction qToSavingsAccountTransaction = new QSavingsAccountTransaction("toSavingsTrans"); + + JPAQuery query = new JPAQuery<>(entityManager); + + query.select(qAccountTransferTransaction.id, qAccountTransferTransaction.reversed.as("isReversed"), + qAccountTransferTransaction.date.as("transferDate"), qAccountTransferTransaction.amount.as("transferAmount"), + qAccountTransferTransaction.description.as("transferDescription"), + qAccountTransferTransaction.currency.code.as("currencyCode"), + qAccountTransferTransaction.currency.digitsAfterDecimal.as("currencyDigits"), + qAccountTransferTransaction.currency.inMultiplesOf, qApplicationCurrency.name.as("currencyName"), + qApplicationCurrency.nameCode.as("currencyNameCode"), qApplicationCurrency.displaySymbol.as("currencyDisplaySymbol"), + qFromOffice.id.as("fromOfficeId"), qFromOffice.name.as("fromOfficeName"), qToOffice.id.as("toOfficeId"), + qToOffice.name.as("toOfficeName"), qFromClient.id.as("fromClientId"), qFromClient.displayName.as("fromClientName"), + qToClient.id.as("toClientId"), qToClient.displayName.as("toClientName"), qFromSavingsAccount.id.as("fromSavingsAccountId"), + qFromSavingsAccount.accountNumber.as("fromSavingsAccountNo"), qFromLoan.id.as("fromLoanAccountId"), + qFromLoan.accountNumber.as("fromLoanAccountNo"), qToSavingsAccount.id.as("toSavingsAccountId"), + qToSavingsAccount.accountNumber.as("toSavingsAccountNo"), qToLoan.id.as("toLoanAccountId"), + qToLoan.accountNumber.as("toLoanAccountNo"), qFromSavingsAccountTransaction.id.as("fromSavingsAccountTransactionId"), + qFromSavingsAccountTransaction.typeOf.as("fromSavingsAccountTransactionType"), + qToSavingsAccountTransaction.id.as("toSavingsAccountTransactionId"), + qToSavingsAccountTransaction.typeOf.as("toSavingsAccountTransactionType")).from(qAccountTransferTransaction); + + addJoinsToAccountTransferQuery(query); + return query; + } - return AccountTransferData.instance(id, reversed, transferDate, currency, transferAmount, transferDescription, fromOffice, - toOffice, fromClient, toClient, fromAccountType, fromAccount, toAccountType, toAccount); - } + private void addJoinsToAccountTransferQuery(final JPAQuery selectFromQuery) { + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final QAccountTransferDetails qAccountTransferDetails = QAccountTransferDetails.accountTransferDetails; + final QApplicationCurrency qApplicationCurrency = QApplicationCurrency.applicationCurrency; + final QOffice qFromOffice = new QOffice("fromOffice"); + final QOffice qToOffice = new QOffice("toOffice"); + final QClient qFromClient = new QClient("fromClient"); + final QClient qToClient = new QClient("toClient"); + final QSavingsAccount qFromSavingsAccount = new QSavingsAccount("fromSavings"); + final QSavingsAccount qToSavingsAccount = new QSavingsAccount("toSavings"); + final QLoan qFromLoan = new QLoan("fromLoan"); + final QLoan qToLoan = new QLoan("toLoan"); + final QSavingsAccountTransaction qFromSavingsAccountTransaction = new QSavingsAccountTransaction("fromSavingsTrans"); + final QSavingsAccountTransaction qToSavingsAccountTransaction = new QSavingsAccountTransaction("toSavingsTrans"); + final QLoanTransaction qFromLoanTransaction = new QLoanTransaction("fromLoanTrans"); + final QLoanTransaction qToLoanTransaction = new QLoanTransaction("toLoanTrans"); + + selectFromQuery.leftJoin(qAccountTransferTransaction.accountTransferDetails, qAccountTransferDetails) + .on(qAccountTransferDetails.id.eq(qAccountTransferTransaction.accountTransferDetails.id)).join(qApplicationCurrency) + .on(qApplicationCurrency.code.eq(qAccountTransferTransaction.currency.code)) + .join(qAccountTransferDetails.fromOffice, qFromOffice).on(qFromOffice.id.eq(qAccountTransferDetails.fromOffice.id)) + .join(qAccountTransferDetails.toOffice, qToOffice).on(qToOffice.id.eq(qAccountTransferDetails.toOffice.id)) + .join(qAccountTransferDetails.fromClient, qFromClient).on(qFromClient.id.eq(qAccountTransferDetails.fromClient.id)) + .join(qAccountTransferDetails.toClient, qToClient).on(qToClient.id.eq(qAccountTransferDetails.toClient.id)) + .join(qAccountTransferDetails.fromSavingsAccount, qFromSavingsAccount) + .on(qFromSavingsAccount.id.eq(qAccountTransferDetails.fromSavingsAccount.id)) + .join(qAccountTransferDetails.fromLoanAccount, qFromLoan).on(qFromLoan.id.eq(qAccountTransferDetails.fromLoanAccount.id)) + .join(qAccountTransferDetails.toSavingsAccount, qToSavingsAccount) + .on(qToSavingsAccount.id.eq(qAccountTransferDetails.toSavingsAccount.id)) + .join(qAccountTransferDetails.toLoanAccount, qToLoan).on(qToLoan.id.eq(qAccountTransferDetails.toLoanAccount.id)) + .join(qAccountTransferTransaction.fromSavingsTransaction, qFromSavingsAccountTransaction) + .on(qFromSavingsAccountTransaction.id.eq(qAccountTransferTransaction.fromSavingsTransaction.id)) + .join(qAccountTransferTransaction.toSavingsTransaction, qToSavingsAccountTransaction) + .on(qToSavingsAccountTransaction.id.eq(qAccountTransferTransaction.toSavingsTransaction.id)) + .join(qAccountTransferTransaction.fromLoanTransaction, qFromLoanTransaction) + .on(qFromLoanTransaction.id.eq(qAccountTransferTransaction.fromLoanTransaction.id)) + .join(qAccountTransferTransaction.toLoanTransaction, qToLoanTransaction) + .on(qToLoanTransaction.id.eq(qAccountTransferTransaction.toLoanTransaction.id)); } + private JPAQuery getAccountTransferCountQuery() { + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final JPAQuery query = new JPAQuery<>(entityManager); + + query.select(ONE.count()).from(qAccountTransferTransaction); + addJoinsToAccountTransferQuery(query); + return query; + } + + private BooleanExpression eq(final SimpleExpression expression, final T value) { + return value == null ? expression.isNull() : expression.eq(value); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java index 58dfc01bae3..a422c1a6698 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java @@ -26,6 +26,10 @@ import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferDateParamName; import com.google.common.collect.Lists; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.jpa.impl.JPAQuery; +import jakarta.persistence.EntityManager; import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @@ -51,6 +55,7 @@ import org.apache.fineract.portfolio.account.domain.AccountTransferRepository; import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction; import org.apache.fineract.portfolio.account.domain.AccountTransferType; +import org.apache.fineract.portfolio.account.domain.QAccountTransferTransaction; import org.apache.fineract.portfolio.account.exception.DifferentCurrenciesException; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -88,6 +93,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer private final ConfigurationDomainService configurationDomainService; private final ExternalIdFactory externalIdFactory; private final FineractProperties fineractProperties; + private final EntityManager entityManager; @Transactional @Override @@ -213,9 +219,14 @@ public CommandProcessingResult create(final JsonCommand command) { public void reverseTransfersWithFromAccountType(final Long accountNumber, final PortfolioAccountType accountTypeId) { List acccountTransfers = null; if (accountTypeId.isLoanAccount()) { - acccountTransfers = this.accountTransferRepository.findByFromLoanId(accountNumber); + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final JPAQuery query = new JPAQuery<>(entityManager); + acccountTransfers = query.select(qAccountTransferTransaction).from(qAccountTransferTransaction) + .where(eq(qAccountTransferTransaction.accountTransferDetails.fromLoanAccount.id, accountNumber) + .and(qAccountTransferTransaction.reversed.isFalse())) + .fetch(); } - if (acccountTransfers != null && acccountTransfers.size() > 0) { + if (acccountTransfers != null && !acccountTransfers.isEmpty()) { undoTransactions(acccountTransfers); } @@ -225,14 +236,19 @@ public void reverseTransfersWithFromAccountType(final Long accountNumber, final @Transactional public void reverseTransfersWithFromAccountTransactions(final Collection fromTransactionIds, final PortfolioAccountType accountTypeId) { - List acccountTransfers = new ArrayList<>(); + List accountTransfers = new ArrayList<>(); if (accountTypeId.isLoanAccount()) { List> partitions = Lists.partition(fromTransactionIds.stream().toList(), fineractProperties.getQuery().getInClauseParameterSizeLimit()); - partitions.forEach(partition -> acccountTransfers.addAll(this.accountTransferRepository.findByFromLoanTransactions(partition))); + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final JPAQuery query = new JPAQuery<>(entityManager); + partitions.forEach(partition -> accountTransfers.addAll(query.select(qAccountTransferTransaction) + .from(qAccountTransferTransaction).where(qAccountTransferTransaction.fromLoanTransaction.id.in(partition) + .and(qAccountTransferTransaction.reversed.isFalse())) + .fetch())); } - if (acccountTransfers.size() > 0) { - undoTransactions(acccountTransfers); + if (!accountTransfers.isEmpty()) { + undoTransactions(accountTransfers); } } @@ -242,9 +258,16 @@ public void reverseTransfersWithFromAccountTransactions(final Collection f public void reverseAllTransactions(final Long accountId, final PortfolioAccountType accountTypeId) { List acccountTransfers = null; if (accountTypeId.isLoanAccount()) { - acccountTransfers = this.accountTransferRepository.findAllByLoanId(accountId); + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final JPAQuery query = new JPAQuery<>(entityManager); + + acccountTransfers = query.select(qAccountTransferTransaction).from(qAccountTransferTransaction) + .where(qAccountTransferTransaction.reversed.isFalse() + .and(eq(qAccountTransferTransaction.accountTransferDetails.fromLoanAccount.id, accountId) + .or(eq(qAccountTransferTransaction.accountTransferDetails.toLoanAccount.id, accountId)))) + .orderBy(qAccountTransferTransaction.id.desc()).fetch(); } - if (acccountTransfers != null && acccountTransfers.size() > 0) { + if (acccountTransfers != null && !acccountTransfers.isEmpty()) { undoTransactions(acccountTransfers); } } @@ -277,15 +300,15 @@ private void undoTransactions(final List acccountTra @Override @Transactional public Long transferFunds(final AccountTransferDTO accountTransferDTO) { - Long transferTransactionId = null; + Long transferTransactionId; final boolean isAccountTransfer = true; final boolean isRegularTransaction = accountTransferDTO.isRegularTransaction(); final boolean backdatedTxnsAllowedTill = false; AccountTransferDetails accountTransferDetails = accountTransferDTO.getAccountTransferDetails(); if (isSavingsToLoanAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) { // - SavingsAccount fromSavingsAccount = null; - Loan toLoanAccount = null; + SavingsAccount fromSavingsAccount; + Loan toLoanAccount; if (accountTransferDetails == null) { if (accountTransferDTO.getFromSavingsAccount() == null) { fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId(), @@ -407,8 +430,8 @@ public Long transferFunds(final AccountTransferDTO accountTransferDTO) { } else if (isLoanToSavingsAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) { - Loan fromLoanAccount = null; - SavingsAccount toSavingsAccount = null; + Loan fromLoanAccount; + SavingsAccount toSavingsAccount; if (accountTransferDetails == null) { if (accountTransferDTO.getLoan() == null) { fromLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId()); @@ -423,7 +446,7 @@ public Long transferFunds(final AccountTransferDTO accountTransferDTO) { toSavingsAccount = accountTransferDetails.toSavingsAccount(); this.savingsAccountAssembler.setHelpers(toSavingsAccount); } - LoanTransaction loanTransaction = null; + LoanTransaction loanTransaction; ExternalId txnExternalId = accountTransferDTO.getTxnExternalId(); // Safety net (it might need to generate new one) @@ -467,14 +490,14 @@ public Long transferFunds(final AccountTransferDTO accountTransferDTO) { @Override public AccountTransferDetails repayLoanWithTopup(AccountTransferDTO accountTransferDTO) { final boolean isAccountTransfer = true; - Loan fromLoanAccount = null; + Loan fromLoanAccount; if (accountTransferDTO.getFromLoan() == null) { fromLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId()); } else { fromLoanAccount = accountTransferDTO.getFromLoan(); this.loanAccountAssembler.setHelpers(fromLoanAccount); } - Loan toLoanAccount = null; + Loan toLoanAccount; if (accountTransferDTO.getToLoan() == null) { toLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId()); } else { @@ -505,7 +528,12 @@ public AccountTransferDetails repayLoanWithTopup(AccountTransferDTO accountTrans @Override @Transactional public void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { - final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final JPAQuery query = new JPAQuery<>(entityManager); + final AccountTransferTransaction transferTransaction = query.select(qAccountTransferTransaction).from(qAccountTransferTransaction) + .where(eq(qAccountTransferTransaction.toLoanTransaction.id, loanTransactionId) + .and(qAccountTransferTransaction.reversed.isFalse())) + .fetchOne(); if (transferTransaction != null) { transferTransaction.updateToLoanTransaction(newLoanTransaction); this.accountTransferRepository.save(transferTransaction); @@ -578,4 +606,8 @@ public CommandProcessingResult refundByTransfer(JsonCommand command) { return builder.build(); } + + private BooleanExpression eq(final SimpleExpression expression, final T value) { + return value == null ? expression.isNull() : expression.eq(value); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java index 61d5beb0fbb..a8134151f7a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/PortfolioAccountReadPlatformServiceImpl.java @@ -18,38 +18,35 @@ */ package org.apache.fineract.portfolio.account.service; -import java.math.BigDecimal; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQuery; +import jakarta.persistence.EntityManager; +import java.time.LocalDate; import java.util.Collection; -import java.util.List; -import org.apache.fineract.infrastructure.core.domain.JdbcSupport; -import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; -import org.apache.fineract.organisation.monetary.data.CurrencyData; +import java.util.Collections; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.organisation.monetary.domain.QApplicationCurrency; +import org.apache.fineract.organisation.staff.domain.QStaff; import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO; import org.apache.fineract.portfolio.account.data.PortfolioAccountData; import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; - +import org.apache.fineract.portfolio.client.domain.QClient; +import org.apache.fineract.portfolio.group.domain.QGroup; +import org.apache.fineract.portfolio.loanaccount.domain.QLoan; +import org.apache.fineract.portfolio.loanaccount.domain.QLoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanproduct.domain.QLoanProduct; +import org.apache.fineract.portfolio.savings.domain.QSavingsAccount; +import org.apache.fineract.portfolio.savings.domain.QSavingsProduct; + +@AllArgsConstructor public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccountReadPlatformService { - private final JdbcTemplate jdbcTemplate; - - // mapper - private final PortfolioSavingsAccountMapper savingsAccountMapper; - private final PortfolioLoanAccountMapper loanAccountMapper; - private final PortfolioLoanAccountRefundByTransferMapper accountRefundByTransferMapper; - - public PortfolioAccountReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, DatabaseSpecificSQLGenerator sqlGenerator) { - this.jdbcTemplate = jdbcTemplate; - this.savingsAccountMapper = new PortfolioSavingsAccountMapper(); - this.loanAccountMapper = new PortfolioLoanAccountMapper(); - this.accountRefundByTransferMapper = new PortfolioLoanAccountRefundByTransferMapper(sqlGenerator); - } + private final EntityManager entityManager; @Override public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId) { @@ -59,338 +56,188 @@ public PortfolioAccountData retrieveOne(final Long accountId, final Integer acco @Override public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId, final String currencyCode) { - Object[] sqlParams = new Object[] { accountId }; - PortfolioAccountData accountData = null; - try { - String sql = null; - final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId); - switch (accountType) { - case INVALID: - break; - case LOAN: - - sql = "select " + this.loanAccountMapper.schema() + " where la.id = ?"; - if (currencyCode != null) { - sql += " and la.currency_code = ?"; - sqlParams = new Object[] { accountId, currencyCode }; - } - - accountData = this.jdbcTemplate.queryForObject(sql, this.loanAccountMapper, sqlParams); - break; - case SAVINGS: - sql = "select " + this.savingsAccountMapper.schema() + " where sa.id = ?"; - if (currencyCode != null) { - sql += " and sa.currency_code = ?"; - sqlParams = new Object[] { accountId, currencyCode }; - } - - accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams); - break; + final PortfolioAccountData accountData; + final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId); + accountData = switch (accountType) { + case INVALID -> null; + case LOAN -> { + final QLoan qLoan = QLoan.loan; + final JPAQuery loanQuery = getLoanPortfolioAccountSelectQuery(); + BooleanExpression loanPredicate = eq(qLoan.id, accountId); + if (currencyCode != null) { + loanPredicate = loanPredicate.and(qLoan.loanRepaymentScheduleDetail.currency.code.eq(currencyCode)); + } + yield loanQuery.where(loanPredicate).fetchOne(); } - } catch (final EmptyResultDataAccessException e) { - throw new AccountTransferNotFoundException(accountId, e); - } + case SAVINGS -> { + final QSavingsAccount qSavingsAccount = QSavingsAccount.savingsAccount; + final JPAQuery savingsQuery = getSavingsPortfolioAccountSelectQuery(); + BooleanExpression savingsPredicate = eq(qSavingsAccount.id, accountId); + if (currencyCode != null) { + savingsPredicate = savingsPredicate.and(qSavingsAccount.currency.code.eq(currencyCode)); + } + yield savingsQuery.where(savingsPredicate).fetchOne(); + } + }; - return accountData; + return Optional.ofNullable(accountData).orElseThrow(() -> new AccountTransferNotFoundException(accountId)); } @Override public Collection retrieveAllForLookup(final PortfolioAccountDTO portfolioAccountDTO) { - final List sqlParams = new ArrayList<>(); - // sqlParams.add(portfolioAccountDTO.getClientId()); Collection accounts = null; - String sql = null; long defaultAccountStatus = 300; // Active Status if (portfolioAccountDTO.getAccountStatus() != null) { defaultAccountStatus = portfolioAccountDTO.getFirstAccountStatus(); } final PortfolioAccountType accountType = PortfolioAccountType.fromInt(portfolioAccountDTO.getAccountTypeId()); - switch (accountType) { - case INVALID: - break; - case LOAN: - sql = "select " + this.loanAccountMapper.schema() + " where "; + accounts = switch (accountType) { + case INVALID -> Collections.emptyList(); + case LOAN -> { + final QLoan qLoan = QLoan.loan; + JPAQuery loanQuery = getLoanPortfolioAccountSelectQuery(); + BooleanExpression loanPredicate = qLoan.loanStatus.in(defaultAccountStatus); if (portfolioAccountDTO.getClientId() != null) { - sql += " la.client_id = ? and la.loan_status_id in (?) "; - sqlParams.add(portfolioAccountDTO.getClientId()); - sqlParams.add(defaultAccountStatus); - } else { - sql += " la.loan_status_id in (?) "; - sqlParams.add(defaultAccountStatus); + loanPredicate = loanPredicate.and(qLoan.client.id.eq(portfolioAccountDTO.getClientId())); } if (portfolioAccountDTO.getCurrencyCode() != null) { - sql += " and la.currency_code = ?"; - sqlParams.add(portfolioAccountDTO.getCurrencyCode()); + loanPredicate = loanPredicate + .and(qLoan.loanRepaymentScheduleDetail.currency.code.eq(portfolioAccountDTO.getCurrencyCode())); } - - accounts = this.jdbcTemplate.query(sql, this.loanAccountMapper, sqlParams.toArray()); // NOSONAR - break; - case SAVINGS: - sql = "select " + this.savingsAccountMapper.schema() + " where "; + yield loanQuery.where(loanPredicate).fetch(); + } + case SAVINGS -> { + final QSavingsAccount qSavingsAccount = QSavingsAccount.savingsAccount; + JPAQuery savingsQuery = getSavingsPortfolioAccountSelectQuery(); + BooleanExpression savingsPredicate = qSavingsAccount.status.in(defaultAccountStatus); if (portfolioAccountDTO.getClientId() != null) { - sql += " sa.client_id = ? and sa.status_enum in (?) "; - sqlParams.add(portfolioAccountDTO.getClientId()); - sqlParams.add(defaultAccountStatus); - } else { - sql += " sa.status_enum in (?) "; - sqlParams.add(defaultAccountStatus); + savingsPredicate = savingsPredicate.and(qSavingsAccount.client.id.eq(portfolioAccountDTO.getClientId())); + } else if (portfolioAccountDTO.getGroupId() != null) { + savingsPredicate = savingsPredicate.and(qSavingsAccount.group.id.eq(portfolioAccountDTO.getGroupId())); } if (portfolioAccountDTO.getCurrencyCode() != null) { - sql += " and sa.currency_code = ?"; - sqlParams.add(portfolioAccountDTO.getCurrencyCode()); + savingsPredicate = savingsPredicate.and(qSavingsAccount.currency.code.eq(portfolioAccountDTO.getCurrencyCode())); } - if (portfolioAccountDTO.getDepositType() != null) { - sql += " and sa.deposit_type_enum = ?"; - sqlParams.add(portfolioAccountDTO.getDepositType().shortValue()); + savingsPredicate = savingsPredicate.and(qSavingsAccount.depositType.eq(portfolioAccountDTO.getDepositType())); } - if (portfolioAccountDTO.isExcludeOverDraftAccounts()) { - sql += " and sa.allow_overdraft = false"; + savingsPredicate = savingsPredicate.and(qSavingsAccount.allowOverdraft.isFalse()); } - - if (portfolioAccountDTO.getClientId() == null && portfolioAccountDTO.getGroupId() != null) { - sql += " and sa.group_id = ? "; - sqlParams.add(portfolioAccountDTO.getGroupId()); - } - - accounts = this.jdbcTemplate.query(sql, this.savingsAccountMapper, sqlParams.toArray()); // NOSONAR - break; - } + yield savingsQuery.where(savingsPredicate).fetch(); + } + }; return accounts; } - private static final class PortfolioSavingsAccountMapper implements RowMapper { - - private final String schemaSql; - - PortfolioSavingsAccountMapper() { - - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, "); - sqlBuilder.append("c.id as clientId, c.display_name as clientName, "); - sqlBuilder.append("g.id as groupId, g.display_name as groupName, "); - sqlBuilder.append("sp.id as productId, sp.name as productName, "); - sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, "); - sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits,"); - sqlBuilder.append("sa.currency_multiplesof as inMultiplesOf, "); - sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol "); - sqlBuilder.append("from m_savings_account sa "); - sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id "); - sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); - sqlBuilder.append("left join m_client c ON c.id = sa.client_id "); - sqlBuilder.append("left join m_group g ON g.id = sa.group_id "); - sqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id "); - - this.schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong("id"); - final String accountNo = rs.getString("accountNo"); - final String externalId = rs.getString("externalId"); - - final Long groupId = JdbcSupport.getLong(rs, "groupId"); - final String groupName = rs.getString("groupName"); - final Long clientId = JdbcSupport.getLong(rs, "clientId"); - final String clientName = rs.getString("clientName"); - - final Long productId = rs.getLong("productId"); - final String productName = rs.getString("productName"); - - final Long fieldOfficerId = rs.getLong("fieldOfficerId"); - final String fieldOfficerName = rs.getString("fieldOfficerName"); - - final String currencyCode = rs.getString("currencyCode"); - final String currencyName = rs.getString("currencyName"); - final String currencyNameCode = rs.getString("currencyNameCode"); - final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol, - currencyNameCode); - - return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, - fieldOfficerId, fieldOfficerName, currency); - } - } - - private static final class PortfolioLoanAccountMapper implements RowMapper { - - private final String schemaSql; - - PortfolioLoanAccountMapper() { - - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, "); - sqlBuilder.append("c.id as clientId, c.display_name as clientName, "); - sqlBuilder.append("g.id as groupId, g.display_name as groupName, "); - sqlBuilder.append("lp.id as productId, lp.name as productName, "); - sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, "); - sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,"); - sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, "); - sqlBuilder.append("la.total_overpaid_derived as totalOverpaid, "); - sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol "); - sqlBuilder.append("from m_loan la "); - sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id "); - sqlBuilder.append("join m_currency curr on curr.code = la.currency_code "); - sqlBuilder.append("left join m_client c ON c.id = la.client_id "); - sqlBuilder.append("left join m_group g ON g.id = la.group_id "); - sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id "); - - this.schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong("id"); - final String accountNo = rs.getString("accountNo"); - final String externalId = rs.getString("externalId"); - - final Long groupId = JdbcSupport.getLong(rs, "groupId"); - final String groupName = rs.getString("groupName"); - final Long clientId = JdbcSupport.getLong(rs, "clientId"); - final String clientName = rs.getString("clientName"); - - final Long productId = rs.getLong("productId"); - final String productName = rs.getString("productName"); - - final Long fieldOfficerId = rs.getLong("fieldOfficerId"); - final String fieldOfficerName = rs.getString("fieldOfficerName"); - - final String currencyCode = rs.getString("currencyCode"); - final String currencyName = rs.getString("currencyName"); - final String currencyNameCode = rs.getString("currencyNameCode"); - final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol, - currencyNameCode); - - return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, - fieldOfficerId, fieldOfficerName, currency, amtForTransfer); - } - } - - private static final class PortfolioLoanAccountRefundByTransferMapper implements RowMapper { - - private String schemaSql; - private final DatabaseSpecificSQLGenerator sqlGenerator; - - PortfolioLoanAccountRefundByTransferMapper(DatabaseSpecificSQLGenerator sqlGenerator) { - this.sqlGenerator = sqlGenerator; - } - - public String schema() { - final StringBuilder amountQueryString = new StringBuilder(400); - amountQueryString.append("(select (SUM(COALESCE(mr.principal_completed_derived, 0)) +"); - amountQueryString.append("SUM(COALESCE(mr.interest_completed_derived, 0)) + "); - amountQueryString.append("SUM(COALESCE(mr.fee_charges_completed_derived, 0)) + "); - amountQueryString.append(" SUM(COALESCE(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived"); - amountQueryString.append(" from m_loan ml INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id"); - amountQueryString.append(" where ml.id=? and ml.loan_status_id = 300"); - amountQueryString.append(" and mr.duedate >= " + sqlGenerator.currentBusinessDate() + " group by ml.id having"); - amountQueryString.append(" (SUM(COALESCE(mr.principal_completed_derived, 0)) + "); - amountQueryString.append(" SUM(COALESCE(mr.interest_completed_derived, 0)) + "); - amountQueryString.append("SUM(COALESCE(mr.fee_charges_completed_derived, 0)) + "); - amountQueryString.append("SUM(COALESCE(mr.penalty_charges_completed_derived, 0))) > 0) as totalOverpaid "); - - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, "); - sqlBuilder.append("c.id as clientId, c.display_name as clientName, "); - sqlBuilder.append("g.id as groupId, g.display_name as groupName, "); - sqlBuilder.append("lp.id as productId, lp.name as productName, "); - sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, "); - sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,"); - sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, "); - sqlBuilder.append(amountQueryString.toString()); - sqlBuilder.append(", "); - sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol "); - sqlBuilder.append("from m_loan la "); - sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id "); - sqlBuilder.append("join m_currency curr on curr.code = la.currency_code "); - sqlBuilder.append("left join m_client c ON c.id = la.client_id "); - sqlBuilder.append("left join m_group g ON g.id = la.group_id "); - sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id "); - - this.schemaSql = sqlBuilder.toString(); - - return this.schemaSql; - } - - @Override - public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong("id"); - final String accountNo = rs.getString("accountNo"); - final String externalId = rs.getString("externalId"); - - final Long groupId = JdbcSupport.getLong(rs, "groupId"); - final String groupName = rs.getString("groupName"); - final Long clientId = JdbcSupport.getLong(rs, "clientId"); - final String clientName = rs.getString("clientName"); - - final Long productId = rs.getLong("productId"); - final String productName = rs.getString("productName"); - - final Long fieldOfficerId = rs.getLong("fieldOfficerId"); - final String fieldOfficerName = rs.getString("fieldOfficerName"); - - final String currencyCode = rs.getString("currencyCode"); - final String currencyName = rs.getString("currencyName"); - final String currencyNameCode = rs.getString("currencyNameCode"); - final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol, - currencyNameCode); - - return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, - fieldOfficerId, fieldOfficerName, currency, amtForTransfer); - } - } - @Override public PortfolioAccountData retrieveOneByPaidInAdvance(Long accountId, Integer accountTypeId) { // TODO Auto-generated method stub - Object[] sqlParams = new Object[] { accountId, accountId }; - PortfolioAccountData accountData = null; - // String currencyCode = null; - try { - String sql = null; - // final PortfolioAccountType accountType = - // PortfolioAccountType.fromInt(accountTypeId); - - sql = "select " + this.accountRefundByTransferMapper.schema() + " where la.id = ?"; - /* - * if (currencyCode != null) { sql += " and la.currency_code = ?"; sqlParams = new Object[] {accountId , - * accountId,currencyCode }; } - */ + // final PortfolioAccountType accountType = + // PortfolioAccountType.fromInt(accountTypeId); + + final QLoan qLoan = QLoan.loan; + final QLoan qLoanSubQuery = new QLoan("loanSubQuery"); + final QLoanRepaymentScheduleInstallment qLoanRepaymentSchedule = QLoanRepaymentScheduleInstallment.loanRepaymentScheduleInstallment; + final QLoanProduct qLoanProduct = QLoanProduct.loanProduct; + final QApplicationCurrency qCurrency = QApplicationCurrency.applicationCurrency; + final QClient qClient = QClient.client; + final QGroup qGroup = QGroup.group; + final QStaff qStaff = QStaff.staff; + + final JPAQuery mainQuery = new JPAQuery<>(); + mainQuery + .select(qLoan.id.as("id"), qLoan.accountNumber.as("accountNo"), qLoan.externalId.as("externalId"), + qClient.id.as("clientId"), qClient.displayName.as("clientName"), qGroup.id.as("groupId"), + qGroup.name.as("groupName"), qLoanProduct.id.as("productId"), qLoanProduct.name.as("productName"), + qStaff.id.as("fieldOfficerId"), qStaff.displayName.as("fieldOfficerName"), + qLoan.loanRepaymentScheduleDetail.currency.code.as("currencyCode"), + qLoan.loanRepaymentScheduleDetail.currency.digitsAfterDecimal.as("currencyDigits"), + qLoan.loanRepaymentScheduleDetail.currency.inMultiplesOf.as("inMultiplesOf"), + JPAExpressions + .select(qLoanRepaymentSchedule.principalCompleted.sumBigDecimal() + .add(qLoanRepaymentSchedule.interestPaid.sumBigDecimal()) + .add(qLoanRepaymentSchedule.feeChargesPaid.sumBigDecimal()) + .add(qLoanRepaymentSchedule.penaltyChargesPaid.sumBigDecimal()).as("totalOverpaid")) + .from(qLoanSubQuery).join(qLoanRepaymentSchedule).on(qLoanSubQuery.id.eq(qLoanRepaymentSchedule.loan.id)) + .where(qLoanRepaymentSchedule.loan.id.eq(qLoan.id).and(qLoan.loanStatus.eq(300)) + .and(qLoanRepaymentSchedule.dueDate.goe(LocalDate + .parse(DateUtils.getBusinessLocalDate().format(DateUtils.DEFAULT_DATE_FORMATTER))))) + .groupBy(qLoanSubQuery.id) + .having(qLoanRepaymentSchedule.principalCompleted.sumBigDecimal() + .add(qLoanRepaymentSchedule.interestPaid.sumBigDecimal()) + .add(qLoanRepaymentSchedule.feeChargesPaid.sumBigDecimal()) + .add(qLoanRepaymentSchedule.penaltyChargesPaid.sumBigDecimal()).gt(0.0)), + qCurrency.name.as("currencyName"), qCurrency.nameCode.as("currencyNameCode"), + qCurrency.displaySymbol.as("currencyDisplaySymbol")) + .from(qLoan).join(qLoan.loanProduct, qLoanProduct).on(qLoanProduct.id.eq(qLoan.loanProduct.id)).join(qCurrency) + .on(qCurrency.code.eq(qLoan.loanRepaymentScheduleDetail.currency.code)).leftJoin(qLoan.client, qClient) + .on(qClient.id.eq(qLoan.client.id)).leftJoin(qLoan.group, qGroup).on(qGroup.id.eq(qLoan.group.id)) + .leftJoin(qLoan.loanOfficer, qStaff).on(qStaff.id.eq(qLoan.loanOfficer.id)).where(qLoan.id.eq(accountId)); + + /* + * if (currencyCode != null) { sql += " and la.currency_code = ?"; sqlParams = new Object[] {accountId , + * accountId,currencyCode }; } + */ + + return Optional.ofNullable(mainQuery.fetchOne()).orElseThrow(() -> new AccountTransferNotFoundException(accountId)); + } - accountData = this.jdbcTemplate.queryForObject(sql, this.accountRefundByTransferMapper, sqlParams); + private JPAQuery getLoanPortfolioAccountSelectQuery() { + final QLoan qLoan = QLoan.loan; + final QLoanProduct qLoanProduct = QLoanProduct.loanProduct; + final QApplicationCurrency qApplicationCurrency = QApplicationCurrency.applicationCurrency; + final QClient qClient = QClient.client; + final QGroup qGroup = QGroup.group; + final QStaff qStaff = QStaff.staff; + + final JPAQuery loanQuery = new JPAQuery<>(entityManager); + loanQuery + .select(qLoan.id, qLoan.accountNumber.as("accountNo"), qLoan.externalId, qClient.id.as("clientId"), + qClient.displayName.as("clientName"), qGroup.id.as("groupId"), qGroup.name.as("groupName"), + qLoanProduct.id.as("productId"), qLoanProduct.name.as("productName"), qStaff.id.as("fieldOfficerId"), + qStaff.displayName.as("fieldOfficerName"), qLoan.loanRepaymentScheduleDetail.currency.code.as("currencyCode"), + qLoan.loanRepaymentScheduleDetail.currency.digitsAfterDecimal.as("currencyDigits"), + qLoan.loanRepaymentScheduleDetail.currency.inMultiplesOf, qLoan.totalOverpaid, + qApplicationCurrency.name.as("currencyName"), qApplicationCurrency.code.as("currencyNameCode"), + qApplicationCurrency.displaySymbol.as("currencyDisplaySymbol")) + .from(qLoan).join(qLoan.loanProduct, qLoanProduct).on(qLoanProduct.id.eq(qLoan.loanProduct.id)).join(qApplicationCurrency) + .on(qApplicationCurrency.code.eq(qLoan.loanRepaymentScheduleDetail.currency.code)).leftJoin(qLoan.client, qClient) + .on(qClient.id.eq(qLoan.client.id)).leftJoin(qLoan.group, qGroup).on(qGroup.id.eq(qLoan.group.id)) + .leftJoin(qLoan.loanOfficer, qStaff).on(qStaff.id.eq(qLoan.loanOfficer.id)); + + return loanQuery; + } - } catch (final EmptyResultDataAccessException e) { - throw new AccountTransferNotFoundException(accountId, e); - } + private JPAQuery getSavingsPortfolioAccountSelectQuery() { + final QSavingsAccount qSavingsAccount = QSavingsAccount.savingsAccount; + final QSavingsProduct qSavingsProduct = QSavingsProduct.savingsProduct; + final QApplicationCurrency qApplicationCurrency = QApplicationCurrency.applicationCurrency; + final QClient qClient = QClient.client; + final QGroup qGroup = QGroup.group; + final QStaff qStaff = QStaff.staff; + + final JPAQuery savingsQuery = new JPAQuery<>(entityManager); + savingsQuery + .select(qSavingsAccount.id, qSavingsAccount.accountNumber.as("accountNo"), qSavingsAccount.externalId, + qClient.id.as("clientId"), qClient.displayName.as("clientName"), qGroup.id.as("groupId"), + qGroup.name.as("groupName"), qSavingsProduct.id.as("productId"), qSavingsProduct.name.as("productName"), + qStaff.id.as("fieldOfficerId"), qStaff.displayName.as("fieldOfficerName"), + qSavingsAccount.currency.code.as("currencyCode"), qSavingsAccount.currency.digitsAfterDecimal.as("currencyDigits"), + qSavingsAccount.currency.inMultiplesOf, qApplicationCurrency.name.as("currencyName"), + qApplicationCurrency.code.as("currencyNameCode"), qApplicationCurrency.displaySymbol.as("currencyDisplaySymbol")) + .from(qSavingsAccount).join(qSavingsProduct).on(qSavingsAccount.product.id.eq(qSavingsProduct.id)) + .join(qApplicationCurrency).on(qApplicationCurrency.code.eq(qSavingsAccount.currency.code)) + .leftJoin(qSavingsAccount.client, qClient).on(qClient.id.eq(qSavingsAccount.client.id)) + .leftJoin(qSavingsAccount.group, qGroup).on(qGroup.id.eq(qSavingsAccount.group.id)) + .leftJoin(qSavingsAccount.savingsOfficer, qStaff).on(qStaff.id.eq(qSavingsAccount.savingsOfficer.id)); + + return savingsQuery; + } - return accountData; + private BooleanExpression eq(final SimpleExpression expression, final T value) { + return value == null ? expression.isNull() : expression.eq(value); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java index b663de054e5..38b5006b545 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java @@ -18,262 +18,356 @@ */ package org.apache.fineract.portfolio.account.service; +import static com.querydsl.core.types.dsl.Expressions.ONE; +import static java.util.stream.Collectors.toList; import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.accountType; -import java.math.BigDecimal; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.LocalDate; -import java.util.ArrayList; +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import jakarta.persistence.EntityManager; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import lombok.AllArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; -import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.PaginationHelper; import org.apache.fineract.infrastructure.core.service.SearchParameters; -import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; -import org.apache.fineract.infrastructure.security.utils.ColumnValidator; import org.apache.fineract.organisation.office.data.OfficeData; +import org.apache.fineract.organisation.office.domain.QOffice; import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.data.PortfolioAccountData; import org.apache.fineract.portfolio.account.data.StandingInstructionDTO; import org.apache.fineract.portfolio.account.data.StandingInstructionHistoryData; +import org.apache.fineract.portfolio.account.domain.QAccountTransferDetails; +import org.apache.fineract.portfolio.account.domain.QAccountTransferStandingInstruction; +import org.apache.fineract.portfolio.account.domain.QAccountTransferStandingInstructionsHistory; import org.apache.fineract.portfolio.client.data.ClientData; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; +import org.apache.fineract.portfolio.client.domain.QClient; +import org.apache.fineract.portfolio.loanaccount.domain.QLoan; +import org.apache.fineract.portfolio.loanproduct.domain.QLoanProduct; +import org.apache.fineract.portfolio.savings.domain.QSavingsAccount; +import org.apache.fineract.portfolio.savings.domain.QSavingsProduct; +@AllArgsConstructor public class StandingInstructionHistoryReadPlatformServiceImpl implements StandingInstructionHistoryReadPlatformService { - private final JdbcTemplate jdbcTemplate; - private final DatabaseSpecificSQLGenerator sqlGenerator; - private final ColumnValidator columnValidator; - - // mapper - private final StandingInstructionHistoryMapper standingInstructionHistoryMapper; - // pagination private final PaginationHelper paginationHelper; - - public StandingInstructionHistoryReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, final ColumnValidator columnValidator, - DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) { - this.jdbcTemplate = jdbcTemplate; - this.sqlGenerator = sqlGenerator; - this.standingInstructionHistoryMapper = new StandingInstructionHistoryMapper(); - this.columnValidator = columnValidator; - this.paginationHelper = paginationHelper; - } + private final EntityManager entityManager; @Override public Page retrieveAll(StandingInstructionDTO standingInstructionDTO) { + final QAccountTransferStandingInstructionsHistory qHistory = QAccountTransferStandingInstructionsHistory.accountTransferStandingInstructionsHistory; + final QAccountTransferDetails qAccountTransferDetails = QAccountTransferDetails.accountTransferDetails; + final QClient qFromClient = new QClient("fromClient"); + final QSavingsAccount qFromSavingsAccount = new QSavingsAccount("fromSavingsAccount"); + final QLoan qFromLoanAccount = new QLoan("fromLoanAccount"); + + final JPAQuery query = getStandingInstructionHistorySelectQuery(); + final JPAQuery totalCountQuery = getStandingInstructionHistoryCountQuery(); + BooleanExpression whereClause = null; - final StringBuilder sqlBuilder = new StringBuilder(200); - sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); - sqlBuilder.append(this.standingInstructionHistoryMapper.schema()); - if (standingInstructionDTO.transferType() != null || standingInstructionDTO.clientId() != null - || standingInstructionDTO.clientName() != null - || (standingInstructionDTO.fromAccountType() != null && standingInstructionDTO.fromAccount() != null) - || standingInstructionDTO.startDateRange() != null || standingInstructionDTO.endDateRange() != null) { - sqlBuilder.append(" where "); - } - boolean addAndCaluse = false; - List paramObj = new ArrayList<>(); if (standingInstructionDTO.transferType() != null) { - if (addAndCaluse) { - sqlBuilder.append(" and "); - } - sqlBuilder.append(" atd.transfer_type=? "); - paramObj.add(standingInstructionDTO.transferType()); - addAndCaluse = true; + whereClause = addCondition(null, qAccountTransferDetails.transferType.eq(standingInstructionDTO.transferType())); } if (standingInstructionDTO.clientId() != null) { - if (addAndCaluse) { - sqlBuilder.append(" and "); - } - sqlBuilder.append(" fromclient.id=? "); - paramObj.add(standingInstructionDTO.clientId()); - addAndCaluse = true; + whereClause = addCondition(whereClause, qFromClient.id.eq(standingInstructionDTO.clientId())); } else if (standingInstructionDTO.clientName() != null) { - if (addAndCaluse) { - sqlBuilder.append(" and "); - } - sqlBuilder.append(" fromclient.display_name=? "); - paramObj.add(standingInstructionDTO.clientName()); - addAndCaluse = true; + whereClause = addCondition(whereClause, qFromClient.displayName.eq(standingInstructionDTO.clientName())); } if (standingInstructionDTO.fromAccountType() != null && standingInstructionDTO.fromAccount() != null) { PortfolioAccountType accountType = PortfolioAccountType.fromInt(standingInstructionDTO.fromAccountType()); - if (addAndCaluse) { - sqlBuilder.append(" and "); - } if (accountType.isSavingsAccount()) { - sqlBuilder.append(" fromsavacc.id=? "); - paramObj.add(standingInstructionDTO.fromAccount()); + whereClause = addCondition(whereClause, qFromSavingsAccount.id.eq(standingInstructionDTO.fromAccount())); } else if (accountType.isLoanAccount()) { - sqlBuilder.append(" fromloanacc.id=? "); - paramObj.add(standingInstructionDTO.fromAccount()); + whereClause = addCondition(whereClause, qFromLoanAccount.id.eq(standingInstructionDTO.fromAccount())); } - addAndCaluse = true; } + final ZoneId tenantZone = DateUtils.getDateTimeZoneOfTenant(); + if (standingInstructionDTO.startDateRange() != null) { - if (addAndCaluse) { - sqlBuilder.append(" and "); - } - sqlBuilder.append(" atsih.execution_time >= ? "); - paramObj.add(DateUtils.DEFAULT_DATE_FORMATTER.format(standingInstructionDTO.startDateRange())); - addAndCaluse = true; + LocalDateTime startDateTime = standingInstructionDTO.startDateRange().atStartOfDay(tenantZone).toLocalDateTime(); + whereClause = addCondition(whereClause, qHistory.executionTime.goe(startDateTime)); } if (standingInstructionDTO.endDateRange() != null) { - if (addAndCaluse) { - sqlBuilder.append(" and "); - } - sqlBuilder.append(" atsih.execution_time < ? "); - paramObj.add(DateUtils.DEFAULT_DATE_FORMATTER.format(standingInstructionDTO.endDateRange())); - addAndCaluse = true; + LocalDateTime endDateTime = standingInstructionDTO.endDateRange().atTime(LocalTime.MAX).atZone(tenantZone).toLocalDateTime(); + whereClause = addCondition(whereClause, qHistory.executionTime.loe(endDateTime)); } + query.where(whereClause); + totalCountQuery.where(whereClause); + final SearchParameters searchParameters = standingInstructionDTO.searchParameters(); - if (searchParameters.hasOrderBy()) { - sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); - this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); - if (searchParameters.hasSortOrder()) { - sqlBuilder.append(' ').append(searchParameters.getSortOrder()); - this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); + if (searchParameters != null) { + if (searchParameters.hasOrderBy()) { + final Order order = searchParameters.getSortOrder().equalsIgnoreCase("desc") ? Order.DESC + : searchParameters.getSortOrder().equalsIgnoreCase("asc") || searchParameters.getSortOrder().isEmpty() ? Order.ASC + : null; + if (order == null) { + throw new IllegalArgumentException("Unknown sort order: " + searchParameters.getSortOrder()); + } + + final OrderSpecifier specifier = OrderSpecifierFactory.getSpecifier(searchParameters.getOrderBy(), order); + query.orderBy(specifier); } - } - if (searchParameters.hasLimit()) { - sqlBuilder.append(" "); - if (searchParameters.hasOffset()) { - sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); - } else { - sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); + if (searchParameters.hasLimit()) { + query.limit(searchParameters.getLimit()); + if (searchParameters.hasOffset()) { + query.offset(searchParameters.getOffset()); + } } } - final Object[] finalObjectArray = paramObj.toArray(); - return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), finalObjectArray, - this.standingInstructionHistoryMapper); + final List queryResult = query.fetch(); + return this.paginationHelper.createPageFromItems( + queryResult.isEmpty() ? Collections.emptyList() : mapQueryResultToStandingInstructionHistoryDataList(queryResult), + Objects.requireNonNull(totalCountQuery.fetchOne())); } - private static final class StandingInstructionHistoryMapper implements RowMapper { - - private final String schemaSql; - - StandingInstructionHistoryMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("atsi.id as id,atsi.name as name, "); - sqlBuilder.append("atsih.status as status, atsih.execution_time as executionTime, "); - sqlBuilder.append("atsih.amount as amount, atsih.error_log as errorLog, "); - sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,"); - sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,"); - sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,"); - sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,"); - sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,"); - sqlBuilder.append("fromsp.id as fromProductId, fromsp.name as fromProductName, "); - sqlBuilder.append("fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo,"); - sqlBuilder.append("fromlp.id as fromLoanProductId, fromlp.name as fromLoanProductName,"); - sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,"); - sqlBuilder.append("tosp.id as toProductId, tosp.name as toProductName, "); - sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo, "); - sqlBuilder.append("tolp.id as toLoanProductId, tolp.name as toLoanProductName "); - sqlBuilder.append(" FROM m_account_transfer_standing_instructions_history atsih "); - sqlBuilder.append(" join m_account_transfer_standing_instructions atsi on atsi.id = atsih.standing_instruction_id "); - sqlBuilder.append("join m_account_transfer_details atd on atd.id = atsi.account_transfer_details_id "); - sqlBuilder.append("join m_office fromoff on fromoff.id = atd.from_office_id "); - sqlBuilder.append("join m_office tooff on tooff.id = atd.to_office_id "); - sqlBuilder.append("join m_client fromclient on fromclient.id = atd.from_client_id "); - sqlBuilder.append("join m_client toclient on toclient.id = atd.to_client_id "); - sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id "); - sqlBuilder.append("left join m_savings_product fromsp ON fromsavacc.product_id = fromsp.id "); - sqlBuilder.append("left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id "); - sqlBuilder.append("left join m_product_loan fromlp ON fromloanacc.product_id = fromlp.id "); - sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id "); - sqlBuilder.append("left join m_savings_product tosp ON tosavacc.product_id = tosp.id "); - sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id "); - sqlBuilder.append("left join m_product_loan tolp ON toloanacc.product_id = tolp.id "); - - this.schemaSql = sqlBuilder.toString(); + private static final class OrderSpecifierFactory { + + @FunctionalInterface + interface OrderSpecifierGenerator { + + OrderSpecifier generate(Order order); } - public String schema() { - return this.schemaSql; + private static final Map fieldToSpecifier = new HashMap<>(); + + static { + fieldToSpecifier.put("id", + order -> new OrderSpecifier<>(order, QAccountTransferStandingInstruction.accountTransferStandingInstruction.id)); + fieldToSpecifier.put("name", + order -> new OrderSpecifier<>(order, QAccountTransferStandingInstruction.accountTransferStandingInstruction.name)); + fieldToSpecifier.put("status", order -> new OrderSpecifier<>(order, + QAccountTransferStandingInstructionsHistory.accountTransferStandingInstructionsHistory.status)); + fieldToSpecifier.put("executionTime", order -> new OrderSpecifier<>(order, + QAccountTransferStandingInstructionsHistory.accountTransferStandingInstructionsHistory.executionTime)); + fieldToSpecifier.put("amount", order -> new OrderSpecifier<>(order, + QAccountTransferStandingInstructionsHistory.accountTransferStandingInstructionsHistory.amount)); + fieldToSpecifier.put("errorLog", order -> new OrderSpecifier<>(order, + QAccountTransferStandingInstructionsHistory.accountTransferStandingInstructionsHistory.errorLog)); + fieldToSpecifier.put("fromOfficeId", order -> new OrderSpecifier<>(order, new QOffice("fromOffice").id)); + fieldToSpecifier.put("fromOfficeName", order -> new OrderSpecifier<>(order, new QOffice("fromOffice").name)); + fieldToSpecifier.put("toOfficeId", order -> new OrderSpecifier<>(order, new QOffice("toOffice").id)); + fieldToSpecifier.put("toOfficeName", order -> new OrderSpecifier<>(order, new QOffice("toOffice").name)); + fieldToSpecifier.put("fromClientId", order -> new OrderSpecifier<>(order, new QClient("fromClient").id)); + fieldToSpecifier.put("fromClientName", order -> new OrderSpecifier<>(order, new QClient("fromClient").displayName)); + fieldToSpecifier.put("toClientId", order -> new OrderSpecifier<>(order, new QClient("toClient").id)); + fieldToSpecifier.put("toClientName", order -> new OrderSpecifier<>(order, new QClient("toClient").displayName)); + fieldToSpecifier.put("fromSavingsAccountId", order -> new OrderSpecifier<>(order, new QSavingsAccount("fromSavings").id)); + fieldToSpecifier.put("fromSavingsAccountNo", + order -> new OrderSpecifier<>(order, new QSavingsAccount("fromSavings").accountNumber)); + fieldToSpecifier.put("toSavingsAccountId", order -> new OrderSpecifier<>(order, new QSavingsAccount("toSavings").id)); + fieldToSpecifier.put("toSavingsAccountNo", + order -> new OrderSpecifier<>(order, new QSavingsAccount("toSavings").accountNumber)); + fieldToSpecifier.put("fromLoanAccountId", order -> new OrderSpecifier<>(order, new QLoan("fromLoan").id)); + fieldToSpecifier.put("fromLoanAccountNo", order -> new OrderSpecifier<>(order, new QLoan("fromLoan").accountNumber)); + fieldToSpecifier.put("toLoanAccountId", order -> new OrderSpecifier<>(order, new QLoan("toLoan").id)); + fieldToSpecifier.put("toLoanAccountNo", order -> new OrderSpecifier<>(order, new QLoan("toLoan").accountNumber)); + fieldToSpecifier.put("fromProductId", order -> new OrderSpecifier<>(order, new QSavingsProduct("fromSavingsProduct").id)); + fieldToSpecifier.put("fromProductName", order -> new OrderSpecifier<>(order, new QSavingsProduct("fromSavingsProduct").name)); + fieldToSpecifier.put("toProductId", order -> new OrderSpecifier<>(order, new QSavingsProduct("toSavingsProduct").id)); + fieldToSpecifier.put("toProductName", order -> new OrderSpecifier<>(order, new QSavingsProduct("toSavingsProduct").name)); + fieldToSpecifier.put("fromLoanProductId", order -> new OrderSpecifier<>(order, new QSavingsProduct("fromLoanProduct").id)); + fieldToSpecifier.put("fromLoanProductName", order -> new OrderSpecifier<>(order, new QSavingsProduct("fromLoanProduct").name)); + fieldToSpecifier.put("toLoanProductId", order -> new OrderSpecifier<>(order, new QSavingsProduct("toLoanProduct").id)); + fieldToSpecifier.put("toLoanProductName", order -> new OrderSpecifier<>(order, new QSavingsProduct("toLoanProduct").name)); } - @Override - public StandingInstructionHistoryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong("id"); - final String name = rs.getString("name"); - - final String status = rs.getString("status"); - final LocalDate executionTime = JdbcSupport.getLocalDate(rs, "executionTime"); - final BigDecimal transferAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amount"); - final String errorLog = rs.getString("errorLog"); - - final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId"); - final String fromOfficeName = rs.getString("fromOfficeName"); - final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null); - - final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId"); - final String toOfficeName = rs.getString("toOfficeName"); - final OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null); - - final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId"); - final String fromClientName = rs.getString("fromClientName"); - final ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName); - - final Long toClientId = JdbcSupport.getLong(rs, "toClientId"); - final String toClientName = rs.getString("toClientName"); - final ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName); - - final Long fromSavingsAccountId = JdbcSupport.getLong(rs, "fromSavingsAccountId"); - final String fromSavingsAccountNo = rs.getString("fromSavingsAccountNo"); - final Long fromProductId = JdbcSupport.getLong(rs, "fromProductId"); - final String fromProductName = rs.getString("fromProductName"); - final Long fromLoanAccountId = JdbcSupport.getLong(rs, "fromLoanAccountId"); - final String fromLoanAccountNo = rs.getString("fromLoanAccountNo"); - final Long fromLoanProductId = JdbcSupport.getLong(rs, "fromLoanProductId"); - final String fromLoanProductName = rs.getString("fromLoanProductName"); - PortfolioAccountData fromAccount = null; - EnumOptionData fromAccountType = null; - if (fromSavingsAccountId != null) { - fromAccount = new PortfolioAccountData(fromSavingsAccountId, fromSavingsAccountNo, null, null, null, null, null, - fromProductId, fromProductName, null, null, null); - fromAccountType = accountType(PortfolioAccountType.SAVINGS); - } else if (fromLoanAccountId != null) { - fromAccount = new PortfolioAccountData(fromLoanAccountId, fromLoanAccountNo, null, null, null, null, null, - fromLoanProductId, fromLoanProductName, null, null, null); - fromAccountType = accountType(PortfolioAccountType.LOAN); + public static OrderSpecifier getSpecifier(final String fieldName, final Order order) { + final OrderSpecifierGenerator generator = fieldToSpecifier.get(fieldName); + if (generator != null) { + return generator.generate(order); + } else { + throw new IllegalArgumentException("Unknown order field name: " + fieldName); } + } + } - PortfolioAccountData toAccount = null; - EnumOptionData toAccountType = null; - final Long toSavingsAccountId = JdbcSupport.getLong(rs, "toSavingsAccountId"); - final String toSavingsAccountNo = rs.getString("toSavingsAccountNo"); - final Long toProductId = JdbcSupport.getLong(rs, "toProductId"); - final String toProductName = rs.getString("toProductName"); - final Long toLoanAccountId = JdbcSupport.getLong(rs, "toLoanAccountId"); - final String toLoanAccountNo = rs.getString("toLoanAccountNo"); - final Long toLoanProductId = JdbcSupport.getLong(rs, "toLoanProductId"); - final String toLoanProductName = rs.getString("toLoanProductName"); - - if (toSavingsAccountId != null) { - toAccount = new PortfolioAccountData(toSavingsAccountId, toSavingsAccountNo, null, null, null, null, null, toProductId, - toProductName, null, null, null); - toAccountType = accountType(PortfolioAccountType.SAVINGS); - } else if (toLoanAccountId != null) { - toAccount = new PortfolioAccountData(toLoanAccountId, toLoanAccountNo, null, null, null, null, null, toLoanProductId, - toLoanProductName, null, null, null); - toAccountType = accountType(PortfolioAccountType.LOAN); - } + private JPAQuery getStandingInstructionHistorySelectQuery() { + final QAccountTransferStandingInstructionsHistory qHistory = QAccountTransferStandingInstructionsHistory.accountTransferStandingInstructionsHistory; + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final QOffice qFromOffice = new QOffice("fromOffice"); + final QOffice qToOffice = new QOffice("toOffice"); + final QClient qFromClient = new QClient("fromClient"); + final QClient qToClient = new QClient("toClient"); + final QSavingsAccount qFromSavingsAccount = new QSavingsAccount("fromSavingsAccount"); + final QSavingsAccount qToSavingsAccount = new QSavingsAccount("toSavingsAccount"); + final QLoan qFromLoanAccount = new QLoan("fromLoanAccount"); + final QLoan qToLoanAccount = new QLoan("toLoanAccount"); + final QSavingsProduct qFromSavingsProduct = new QSavingsProduct("fromSavingsProduct"); + final QSavingsProduct qToSavingsProduct = new QSavingsProduct("toSavingsProduct"); + final QLoanProduct qFromLoanProduct = new QLoanProduct("fromLoanProduct"); + final QLoanProduct qToLoanProduct = new QLoanProduct("toLoanProduct"); + + final JPAQuery query = new JPAQuery<>(entityManager); + + query.select(qAccountTransferStandingInstruction.id, qAccountTransferStandingInstruction.name, qHistory.status, + qHistory.executionTime, qHistory.amount, qHistory.errorLog, qFromOffice.id.as("fromOfficeId"), + qFromOffice.name.as("fromOfficeName"), qToOffice.id.as("toOfficeId"), qToOffice.name.as("toOfficeName"), + qFromClient.id.as("fromClientId"), qFromClient.displayName.as("fromClientName"), qToClient.id.as("toClientId"), + qToClient.displayName.as("toClientName"), qFromSavingsAccount.id.as("fromSavingsAccountId"), + qFromSavingsAccount.accountNumber.as("fromSavingsAccountNo"), qToSavingsAccount.id.as("toSavingsAccountId"), + qToSavingsAccount.accountNumber.as("toSavingsAccountNo"), qFromLoanAccount.id.as("fromLoanAccountId"), + qFromLoanAccount.accountNumber.as("fromLoanAccountNo"), qToLoanAccount.id.as("toLoanAccountId"), + qToLoanAccount.accountNumber.as("toLoanAccountNo"), qFromSavingsProduct.id.as("fromProductId"), + qFromSavingsProduct.name.as("fromProductName"), qToSavingsProduct.id.as("toProductId"), + qToSavingsProduct.name.as("toProductName"), qFromLoanProduct.id.as("fromLoanProductId"), + qFromLoanProduct.name.as("fromLoanProductName"), qToLoanProduct.id.as("toLoanProductId"), + qToLoanProduct.name.as("toLoanProductName")).from(qHistory); + + addJoinsToStandingInstructionHistoryQuery(query); + return query; + } + + private List mapQueryResultToStandingInstructionHistoryDataList(final List queryResult) { + return queryResult.stream().map(this::mapQueryResultToStandingInstructionHistoryData).collect(toList()); + } + + private StandingInstructionHistoryData mapQueryResultToStandingInstructionHistoryData(final Tuple queryResult) { + final QAccountTransferStandingInstructionsHistory qHistory = QAccountTransferStandingInstructionsHistory.accountTransferStandingInstructionsHistory; + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final QOffice qFromOffice = new QOffice("fromOffice"); + final QOffice qToOffice = new QOffice("toOffice"); + final QClient qFromClient = new QClient("fromClient"); + final QClient qToClient = new QClient("toClient"); + final QSavingsAccount qFromSavingsAccount = new QSavingsAccount("fromSavingsAccount"); + final QSavingsAccount qToSavingsAccount = new QSavingsAccount("toSavingsAccount"); + final QLoan qFromLoanAccount = new QLoan("fromLoanAccount"); + final QLoan qToLoanAccount = new QLoan("toLoanAccount"); + final QSavingsProduct qFromSavingsProduct = new QSavingsProduct("fromSavingsProduct"); + final QSavingsProduct qToSavingsProduct = new QSavingsProduct("toSavingsProduct"); + final QLoanProduct qFromLoanProduct = new QLoanProduct("fromLoanProduct"); + final QLoanProduct qToLoanProduct = new QLoanProduct("toLoanProduct"); + + final OfficeData fromOffice = OfficeData.dropdown(queryResult.get(qFromOffice.id.as("fromOfficeId")), + queryResult.get(qFromOffice.name.as("fromOfficeName")), null); + final OfficeData toOffice = OfficeData.dropdown(queryResult.get(qToOffice.id.as("toOfficeId")), + queryResult.get(qToOffice.name.as("toOfficeName")), null); + final ClientData fromClient = ClientData.lookup(queryResult.get(qFromClient.id.as("fromClientId")), + queryResult.get(qFromClient.displayName.as("fromClientName")), queryResult.get(qFromOffice.id.as("fromOfficeId")), + queryResult.get(qFromOffice.name.as("fromOfficeName"))); + final ClientData toClient = ClientData.lookup(queryResult.get(qToClient.id.as("toClientId")), + queryResult.get(qToClient.displayName.as("toClientName")), queryResult.get(qToOffice.id.as("toOfficeId")), + queryResult.get(qToOffice.name.as("toOfficeName"))); + + final Long fromSavingsAccountId = queryResult.get(qFromSavingsAccount.id.as("fromSavingsAccountId")); + final String fromSavingsAccountNo = queryResult.get(qFromSavingsAccount.accountNumber.as("fromSavingsAccountNo")); + final Long fromProductId = queryResult.get(qFromSavingsProduct.id.as("fromProductId")); + final String fromProductName = queryResult.get(qFromSavingsProduct.name.as("fromProductName")); + final Long fromLoanAccountId = queryResult.get(qFromLoanAccount.id.as("fromLoanAccountId")); + final String fromLoanAccountNo = queryResult.get(qFromLoanAccount.accountNumber.as("fromLoanAccountNo")); + final Long fromLoanProductId = queryResult.get(qFromLoanProduct.id.as("fromLoanProductId")); + final String fromLoanProductName = queryResult.get(qFromLoanProduct.name.as("fromLoanProductName")); + PortfolioAccountData fromAccount = null; + EnumOptionData fromAccountType = null; + if (fromSavingsAccountId != null) { + fromAccount = new PortfolioAccountData(fromSavingsAccountId, fromSavingsAccountNo, null, null, null, null, null, fromProductId, + fromProductName, null, null, null); + fromAccountType = accountType(PortfolioAccountType.SAVINGS); + } else if (fromLoanAccountId != null) { + fromAccount = new PortfolioAccountData(fromLoanAccountId, fromLoanAccountNo, null, null, null, null, null, fromLoanProductId, + fromLoanProductName, null, null, null); + fromAccountType = accountType(PortfolioAccountType.LOAN); + } - return new StandingInstructionHistoryData(id, name, fromOffice, fromClient, fromAccountType, fromAccount, toAccountType, - toAccount, toOffice, toClient, transferAmount, status, executionTime, errorLog); + PortfolioAccountData toAccount = null; + EnumOptionData toAccountType = null; + final Long toSavingsAccountId = queryResult.get(qToSavingsAccount.id.as("toSavingsAccountId")); + final String toSavingsAccountNo = queryResult.get(qToSavingsAccount.accountNumber.as("toSavingsAccountNo")); + final Long toProductId = queryResult.get(qToSavingsProduct.id.as("toProductId")); + final String toProductName = queryResult.get(qToSavingsProduct.name.as("toProductName")); + final Long toLoanAccountId = queryResult.get(qToLoanAccount.id.as("toLoanAccountId")); + final String toLoanAccountNo = queryResult.get(qToLoanAccount.accountNumber.as("toLoanAccountNo")); + final Long toLoanProductId = queryResult.get(qToLoanProduct.id.as("toLoanProductId")); + final String toLoanProductName = queryResult.get(qToLoanProduct.name.as("toLoanProductName")); + + if (toSavingsAccountId != null) { + toAccount = new PortfolioAccountData(toSavingsAccountId, toSavingsAccountNo, null, null, null, null, null, toProductId, + toProductName, null, null, null); + toAccountType = accountType(PortfolioAccountType.SAVINGS); + } else if (toLoanAccountId != null) { + toAccount = new PortfolioAccountData(toLoanAccountId, toLoanAccountNo, null, null, null, null, null, toLoanProductId, + toLoanProductName, null, null, null); + toAccountType = accountType(PortfolioAccountType.LOAN); } + + return new StandingInstructionHistoryData(queryResult.get(qAccountTransferStandingInstruction.id), + queryResult.get(qAccountTransferStandingInstruction.name), fromOffice, fromClient, fromAccountType, fromAccount, + toAccountType, toAccount, toOffice, toClient, queryResult.get(qHistory.amount), queryResult.get(qHistory.status), + Objects.requireNonNull(queryResult.get(qHistory.executionTime)).atZone(DateUtils.getDateTimeZoneOfTenant()).toInstant() + .atZone(DateUtils.getDateTimeZoneOfTenant()).toLocalDate(), + queryResult.get(qHistory.errorLog)); + } + + private void addJoinsToStandingInstructionHistoryQuery(final JPAQuery selectFromQuery) { + final QAccountTransferStandingInstructionsHistory qHistory = QAccountTransferStandingInstructionsHistory.accountTransferStandingInstructionsHistory; + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final QAccountTransferDetails qAccountTransferDetails = QAccountTransferDetails.accountTransferDetails; + final QOffice qFromOffice = new QOffice("fromOffice"); + final QOffice qToOffice = new QOffice("toOffice"); + final QClient qFromClient = new QClient("fromClient"); + final QClient qToClient = new QClient("toClient"); + final QSavingsAccount qFromSavingsAccount = new QSavingsAccount("fromSavingsAccount"); + final QSavingsAccount qToSavingsAccount = new QSavingsAccount("toSavingsAccount"); + final QLoan qFromLoanAccount = new QLoan("fromLoanAccount"); + final QLoan qToLoanAccount = new QLoan("toLoanAccount"); + final QSavingsProduct qFromSavingsProduct = new QSavingsProduct("fromSavingsProduct"); + final QSavingsProduct qToSavingsProduct = new QSavingsProduct("toSavingsProduct"); + final QLoanProduct qFromLoanProduct = new QLoanProduct("fromLoanProduct"); + final QLoanProduct qToLoanProduct = new QLoanProduct("toLoanProduct"); + + selectFromQuery.join(qHistory.accountTransferStandingInstruction, qAccountTransferStandingInstruction) + .on(qAccountTransferStandingInstruction.id.eq(qHistory.accountTransferStandingInstruction.id)) + .join(qAccountTransferStandingInstruction.accountTransferDetails, qAccountTransferDetails) + .on(qAccountTransferDetails.id.eq(qAccountTransferStandingInstruction.accountTransferDetails.id)) + .join(qAccountTransferDetails.fromOffice, qFromOffice).on(qFromOffice.id.eq(qAccountTransferDetails.fromOffice.id)) + .join(qAccountTransferDetails.toOffice, qToOffice).on(qToOffice.id.eq(qAccountTransferDetails.toOffice.id)) + .join(qAccountTransferDetails.fromClient, qFromClient).on(qFromClient.id.eq(qAccountTransferDetails.fromClient.id)) + .join(qAccountTransferDetails.toClient, qToClient).on(qToClient.id.eq(qAccountTransferDetails.toClient.id)) + .leftJoin(qAccountTransferDetails.fromSavingsAccount, qFromSavingsAccount) + .on(qFromSavingsAccount.id.eq(qAccountTransferDetails.fromSavingsAccount.id)) + .leftJoin(qAccountTransferDetails.toSavingsAccount, qToSavingsAccount) + .on(qToSavingsAccount.id.eq(qAccountTransferDetails.toSavingsAccount.id)) + .leftJoin(qAccountTransferDetails.fromLoanAccount, qFromLoanAccount) + .on(qFromLoanAccount.id.eq(qAccountTransferDetails.fromLoanAccount.id)) + .leftJoin(qAccountTransferDetails.toLoanAccount, qToLoanAccount) + .on(qToLoanAccount.id.eq(qAccountTransferDetails.toLoanAccount.id)) + .leftJoin(qFromSavingsAccount.product, qFromSavingsProduct).on(qFromSavingsProduct.id.eq(qFromSavingsAccount.product.id)) + .leftJoin(qToSavingsAccount.product, qToSavingsProduct).on(qToSavingsProduct.id.eq(qToSavingsAccount.product.id)) + .leftJoin(qFromLoanAccount.loanProduct, qFromLoanProduct).on(qFromLoanProduct.id.eq(qFromLoanAccount.loanProduct.id)) + .leftJoin(qToLoanAccount.loanProduct, qToLoanProduct).on(qToLoanProduct.id.eq(qToLoanAccount.loanProduct.id)); + } + + private JPAQuery getStandingInstructionHistoryCountQuery() { + final QAccountTransferStandingInstructionsHistory qHistory = QAccountTransferStandingInstructionsHistory.accountTransferStandingInstructionsHistory; + final JPAQuery query = new JPAQuery<>(entityManager); + + query.select(ONE.count()).from(qHistory); + addJoinsToStandingInstructionHistoryQuery(query); + return query; } + private BooleanExpression addCondition(final BooleanExpression whereClause, final BooleanExpression condition) { + if (whereClause == null) { + return condition; + } else { + return whereClause.and(condition); + } + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java index 751e1c406e5..bc0351ff898 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.account.service; +import static com.querydsl.core.types.dsl.Expressions.ONE; +import static java.util.stream.Collectors.toList; import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.accountType; import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.recurrenceType; import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.standingInstructionPriority; @@ -25,24 +27,34 @@ import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.standingInstructionType; import static org.apache.fineract.portfolio.account.service.AccountTransferEnumerations.transferType; +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.jpa.impl.JPAQuery; +import jakarta.persistence.EntityManager; import java.math.BigDecimal; -import java.sql.ResultSet; -import java.sql.SQLException; import java.time.LocalDate; import java.time.MonthDay; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import lombok.AllArgsConstructor; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.PaginationHelper; import org.apache.fineract.infrastructure.core.service.SearchParameters; -import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; -import org.apache.fineract.infrastructure.security.utils.ColumnValidator; import org.apache.fineract.organisation.office.data.OfficeData; +import org.apache.fineract.organisation.office.domain.QOffice; import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO; @@ -52,50 +64,35 @@ import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData; import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType; import org.apache.fineract.portfolio.account.domain.AccountTransferType; +import org.apache.fineract.portfolio.account.domain.QAccountTransferDetails; +import org.apache.fineract.portfolio.account.domain.QAccountTransferStandingInstruction; import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority; import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; import org.apache.fineract.portfolio.account.domain.StandingInstructionType; import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException; import org.apache.fineract.portfolio.client.data.ClientData; +import org.apache.fineract.portfolio.client.domain.QClient; import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; import org.apache.fineract.portfolio.common.service.CommonEnumerations; import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; +import org.apache.fineract.portfolio.loanaccount.domain.QLoan; +import org.apache.fineract.portfolio.loanaccount.domain.QLoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanproduct.domain.QLoanProduct; +import org.apache.fineract.portfolio.savings.domain.QSavingsAccount; +import org.apache.fineract.portfolio.savings.domain.QSavingsProduct; import org.springframework.util.CollectionUtils; +@AllArgsConstructor public class StandingInstructionReadPlatformServiceImpl implements StandingInstructionReadPlatformService { - private final JdbcTemplate jdbcTemplate; - private final ColumnValidator columnValidator; private final ClientReadPlatformService clientReadPlatformService; private final OfficeReadPlatformService officeReadPlatformService; private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService; private final DropdownReadPlatformService dropdownReadPlatformService; - private final DatabaseSpecificSQLGenerator sqlGenerator; - - // mapper - private final StandingInstructionMapper standingInstructionMapper; // pagination private final PaginationHelper paginationHelper; - - public StandingInstructionReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, - final ClientReadPlatformService clientReadPlatformService, final OfficeReadPlatformService officeReadPlatformService, - final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService, - final DropdownReadPlatformService dropdownReadPlatformService, final ColumnValidator columnValidator, - DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) { - this.jdbcTemplate = jdbcTemplate; - this.clientReadPlatformService = clientReadPlatformService; - this.officeReadPlatformService = officeReadPlatformService; - this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformService; - this.dropdownReadPlatformService = dropdownReadPlatformService; - this.sqlGenerator = sqlGenerator; - this.standingInstructionMapper = new StandingInstructionMapper(); - this.columnValidator = columnValidator; - this.paginationHelper = paginationHelper; - } + private final EntityManager entityManager; @Override public StandingInstructionData retrieveTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId, @@ -110,24 +107,22 @@ public StandingInstructionData retrieveTemplate(final Long fromOfficeId, final L final EnumOptionData loanAccountType = accountType(PortfolioAccountType.LOAN); final EnumOptionData savingsAccountType = accountType(PortfolioAccountType.SAVINGS); - final Integer mostRelevantFromAccountType = fromAccountType; - Collection fromAccountTypeOptions = null; - Collection toAccountTypeOptions = null; + Collection fromAccountTypeOptions; + Collection toAccountTypeOptions; if (accountTransferType.isAccountTransfer()) { - fromAccountTypeOptions = Arrays.asList(savingsAccountType); - toAccountTypeOptions = Arrays.asList(savingsAccountType); + fromAccountTypeOptions = Collections.singletonList(savingsAccountType); + toAccountTypeOptions = Collections.singletonList(savingsAccountType); } else if (accountTransferType.isLoanRepayment()) { - fromAccountTypeOptions = Arrays.asList(savingsAccountType); - toAccountTypeOptions = Arrays.asList(loanAccountType); + fromAccountTypeOptions = Collections.singletonList(savingsAccountType); + toAccountTypeOptions = Collections.singletonList(loanAccountType); } else { fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType); toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType); } - final Integer mostRelevantToAccountType = toAccountType; - final EnumOptionData fromAccountTypeData = accountType(mostRelevantFromAccountType); - final EnumOptionData toAccountTypeData = accountType(mostRelevantToAccountType); + final EnumOptionData fromAccountTypeData = accountType(fromAccountType); + final EnumOptionData toAccountTypeData = accountType(toAccountType); // from settings OfficeData fromOffice = null; @@ -150,7 +145,7 @@ public StandingInstructionData retrieveTemplate(final Long fromOfficeId, final L if (fromAccountId != null) { Integer accountType; - if (mostRelevantFromAccountType == 1) { + if (fromAccountType == 1) { accountType = PortfolioAccountType.LOAN.getValue(); } else { accountType = PortfolioAccountType.SAVINGS.getValue(); @@ -165,11 +160,10 @@ public StandingInstructionData retrieveTemplate(final Long fromOfficeId, final L fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId); mostRelevantFromOfficeId = fromClient.getOfficeId(); long[] loanStatus = null; - if (mostRelevantFromAccountType == 1) { + if (fromAccountType == 1) { loanStatus = new long[] { 300, 700 }; } - PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(mostRelevantFromAccountType, mostRelevantFromClientId, - loanStatus); + PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(fromAccountType, mostRelevantFromClientId, loanStatus); fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO); } @@ -187,8 +181,7 @@ public StandingInstructionData retrieveTemplate(final Long fromOfficeId, final L Collection toClientOptions = null; if (toAccountId != null && fromAccount != null) { - toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType, - fromAccount.getCurrencyCode()); + toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, toAccountType, fromAccount.getCurrencyCode()); mostRelevantToClientId = toAccount.getClientId(); } @@ -198,7 +191,7 @@ public StandingInstructionData retrieveTemplate(final Long fromOfficeId, final L toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); - toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); + toAccountOptions = retrieveToAccounts(fromAccount, toAccountType, mostRelevantToClientId); } if (mostRelevantToOfficeId != null) { @@ -209,7 +202,7 @@ public StandingInstructionData retrieveTemplate(final Long fromOfficeId, final L if (toClientOptions != null && toClientOptions.size() == 1) { toClient = new ArrayList<>(toClientOptions).get(0); - toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); + toAccountOptions = retrieveToAccounts(fromAccount, toAccountType, mostRelevantToClientId); } } @@ -256,335 +249,417 @@ private Collection retrieveToAccounts(final PortfolioAccou @Override public Page retrieveAll(final StandingInstructionDTO standingInstructionDTO) { - final StringBuilder sqlBuilder = new StringBuilder(200); - sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); - sqlBuilder.append(this.standingInstructionMapper.schema()); + final QAccountTransferDetails qAccountTransferDetails = QAccountTransferDetails.accountTransferDetails; + final QClient qFromClient = new QClient("qFromClient"); + final QSavingsAccount qFromSavingsAccount = new QSavingsAccount("qFromSavingsAccount"); + final QLoan qFromLoan = new QLoan("qFromLoan"); + final JPAQuery query = getStandingInstructionSelectQuery(); + final JPAQuery totalCountQuery = getStandingInstructionCountQuery(); + + BooleanExpression whereClause = null; + if (standingInstructionDTO.transferType() != null || standingInstructionDTO.clientId() != null || standingInstructionDTO.clientName() != null) { - sqlBuilder.append(" where "); - } - boolean addAndCaluse = false; - List paramObj = new ArrayList<>(); - if (standingInstructionDTO.transferType() != null) { - if (addAndCaluse) { - sqlBuilder.append(" and "); + if (standingInstructionDTO.transferType() != null) { + whereClause = addCondition(null, qAccountTransferDetails.transferType.eq(standingInstructionDTO.transferType())); } - sqlBuilder.append(" atd.transfer_type=? "); - paramObj.add(standingInstructionDTO.transferType()); - addAndCaluse = true; - } - if (standingInstructionDTO.clientId() != null) { - if (addAndCaluse) { - sqlBuilder.append(" and "); - } - sqlBuilder.append(" fromclient.id=? "); - paramObj.add(standingInstructionDTO.clientId()); - addAndCaluse = true; - } else if (standingInstructionDTO.clientName() != null) { - if (addAndCaluse) { - sqlBuilder.append(" and "); - } - sqlBuilder.append(" fromclient.display_name=? "); - paramObj.add(standingInstructionDTO.clientName()); - addAndCaluse = true; - } - if (standingInstructionDTO.fromAccountType() != null && standingInstructionDTO.fromAccount() != null) { - PortfolioAccountType accountType = PortfolioAccountType.fromInt(standingInstructionDTO.fromAccountType()); - if (addAndCaluse) { - sqlBuilder.append(" and "); + if (standingInstructionDTO.clientId() != null) { + whereClause = addCondition(whereClause, qFromClient.id.eq(standingInstructionDTO.clientId())); + } else if (standingInstructionDTO.clientName() != null) { + whereClause = addCondition(whereClause, qFromClient.displayName.eq(standingInstructionDTO.clientName())); } - if (accountType.isSavingsAccount()) { - sqlBuilder.append(" fromsavacc.id=? "); - paramObj.add(standingInstructionDTO.fromAccount()); - } else if (accountType.isLoanAccount()) { - sqlBuilder.append(" fromloanacc.id=? "); - paramObj.add(standingInstructionDTO.fromAccount()); + + if (standingInstructionDTO.fromAccountType() != null && standingInstructionDTO.fromAccount() != null) { + PortfolioAccountType accountType = PortfolioAccountType.fromInt(standingInstructionDTO.fromAccountType()); + if (accountType.isSavingsAccount()) { + whereClause = addCondition(whereClause, qFromSavingsAccount.id.eq(standingInstructionDTO.fromAccount())); + } else if (accountType.isLoanAccount()) { + whereClause = addCondition(whereClause, qFromLoan.id.eq(standingInstructionDTO.fromAccount())); + } } - addAndCaluse = true; } + query.where(whereClause); + totalCountQuery.where(whereClause); + final SearchParameters searchParameters = standingInstructionDTO.searchParameters(); if (searchParameters.hasOrderBy()) { - sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); - this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); - if (searchParameters.hasSortOrder()) { - sqlBuilder.append(' ').append(searchParameters.getSortOrder()); - this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); + final Order order = searchParameters.getSortOrder().equalsIgnoreCase("desc") ? Order.DESC + : searchParameters.getSortOrder().equalsIgnoreCase("asc") || searchParameters.getSortOrder().isEmpty() ? Order.ASC + : null; + if (order == null) { + throw new IllegalArgumentException("Unknown sort order: " + searchParameters.getSortOrder()); } + + final OrderSpecifier specifier = OrderSpecifierFactory.getSpecifier(searchParameters.getOrderBy(), order); + query.orderBy(specifier); } if (searchParameters.hasLimit()) { - sqlBuilder.append(" "); + query.limit(searchParameters.getLimit()); if (searchParameters.hasOffset()) { - sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); - } else { - sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); + query.offset(searchParameters.getOffset()); } } - final Object[] finalObjectArray = paramObj.toArray(); - return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), finalObjectArray, this.standingInstructionMapper); + final List queryResult = query.fetch(); + return this.paginationHelper.createPageFromItems( + queryResult.isEmpty() ? Collections.emptyList() : mapQueryResultToStandingInstructionDuesDataList(queryResult), + Objects.requireNonNull(totalCountQuery.fetchOne())); } @Override public Collection retrieveAll(final Integer status) { - final StringBuilder sqlBuilder = new StringBuilder(200); - String businessDate = sqlGenerator.currentBusinessDate(); - sqlBuilder.append("select "); - sqlBuilder.append(this.standingInstructionMapper.schema()); - sqlBuilder - .append(" where atsi.status=? and " + businessDate + " >= atsi.valid_from and (atsi.valid_till IS NULL or " + businessDate - + " < atsi.valid_till) ") - .append(" and (atsi.last_run_date <> " + businessDate + " or atsi.last_run_date IS NULL)") - .append(" ORDER BY atsi.priority DESC"); - return this.jdbcTemplate.query(sqlBuilder.toString(), this.standingInstructionMapper, status); + final String businessDate = DateUtils.getBusinessLocalDate().format(DateUtils.DEFAULT_DATE_FORMATTER); + + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final List queryResult = getStandingInstructionSelectQuery() + .where(eq(qAccountTransferStandingInstruction.status, status) + .and(qAccountTransferStandingInstruction.validFrom.loe(LocalDate.parse(businessDate))) + .and(qAccountTransferStandingInstruction.validTill.isNull() + .or(qAccountTransferStandingInstruction.validTill.gt(LocalDate.parse(businessDate)))) + .and(qAccountTransferStandingInstruction.latsRunDate.ne(LocalDate.parse(businessDate)) + .or(qAccountTransferStandingInstruction.latsRunDate.isNull()))) + .orderBy(new OrderSpecifier<>(Order.DESC, qAccountTransferStandingInstruction.priority)).fetch(); + return queryResult.isEmpty() ? Collections.emptyList() : mapQueryResultToStandingInstructionDuesDataList(queryResult); } @Override public StandingInstructionData retrieveOne(final Long instructionId) { + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final Tuple queryResult = getStandingInstructionSelectQuery().where(eq(qAccountTransferStandingInstruction.id, instructionId)) + .fetchOne(); - try { - final String sql = "select " + this.standingInstructionMapper.schema() + " where atsi.id = ?"; - - return this.jdbcTemplate.queryForObject(sql, this.standingInstructionMapper, new Object[] { instructionId }); // NOSONAR - } catch (final EmptyResultDataAccessException e) { - throw new AccountTransferNotFoundException(instructionId, e); - } + return Optional.ofNullable(queryResult).map(this::mapQueryResultToStandingInstructionDuesData) + .orElseThrow(() -> new AccountTransferNotFoundException(instructionId)); } @Override public StandingInstructionDuesData retriveLoanDuesData(final Long loanId) { - final StandingInstructionLoanDuesMapper rm = new StandingInstructionLoanDuesMapper(); - final String sql = "select " + rm.schema() + " where ml.id= ? and ls.duedate <= " + sqlGenerator.currentBusinessDate() - + " and ls.completed_derived <> 1"; - return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanId }); // NOSONAR + final QLoanRepaymentScheduleInstallment qLoanRepaymentSchedule = QLoanRepaymentScheduleInstallment.loanRepaymentScheduleInstallment; + final QLoan qLoan = QLoan.loan; + final JPAQuery query = getStandingInstructionDuesSelectQuery(); + query.where(eq(qLoan.id, loanId), + qLoanRepaymentSchedule.dueDate + .loe(LocalDate.parse(DateUtils.getBusinessLocalDate().format(DateUtils.DEFAULT_DATE_FORMATTER))), + qLoanRepaymentSchedule.obligationsMet.ne(true)); + return query.fetchOne(); } - private static final class StandingInstructionMapper implements RowMapper { - - private final String schemaSql; - - StandingInstructionMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("atsi.id as id,atsi.name as name, atsi.priority as priority,"); - sqlBuilder.append("atsi.status as status, atsi.instruction_type as instructionType,"); - sqlBuilder.append("atsi.amount as amount,"); - sqlBuilder.append("atsi.valid_from as validFrom, atsi.valid_till as validTill,"); - sqlBuilder.append("atsi.recurrence_type as recurrenceType, atsi.recurrence_frequency as recurrenceFrequency,"); - sqlBuilder.append("atsi.recurrence_interval as recurrenceInterval, atsi.recurrence_on_day as recurrenceOnDay,"); - sqlBuilder.append("atsi.recurrence_on_month as recurrenceOnMonth,"); - sqlBuilder.append("atd.id as accountDetailId,atd.transfer_type as transferType,"); - sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,"); - sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,"); - sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,"); - sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,"); - sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,"); - sqlBuilder.append("fromsp.id as fromProductId, fromsp.name as fromProductName, "); - sqlBuilder.append("fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo,"); - sqlBuilder.append("fromlp.id as fromLoanProductId, fromlp.name as fromLoanProductName,"); - sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,"); - sqlBuilder.append("tosp.id as toProductId, tosp.name as toProductName, "); - sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo, "); - sqlBuilder.append("tolp.id as toLoanProductId, tolp.name as toLoanProductName "); - sqlBuilder.append(" FROM m_account_transfer_standing_instructions atsi "); - sqlBuilder.append("join m_account_transfer_details atd on atd.id = atsi.account_transfer_details_id "); - sqlBuilder.append("join m_office fromoff on fromoff.id = atd.from_office_id "); - sqlBuilder.append("join m_office tooff on tooff.id = atd.to_office_id "); - sqlBuilder.append("join m_client fromclient on fromclient.id = atd.from_client_id "); - sqlBuilder.append("join m_client toclient on toclient.id = atd.to_client_id "); - sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id "); - sqlBuilder.append("left join m_savings_product fromsp ON fromsavacc.product_id = fromsp.id "); - sqlBuilder.append("left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id "); - sqlBuilder.append("left join m_product_loan fromlp ON fromloanacc.product_id = fromlp.id "); - sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id "); - sqlBuilder.append("left join m_savings_product tosp ON tosavacc.product_id = tosp.id "); - sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id "); - sqlBuilder.append("left join m_product_loan tolp ON toloanacc.product_id = tolp.id "); - - this.schemaSql = sqlBuilder.toString(); - } + private static final class OrderSpecifierFactory { + + @FunctionalInterface + interface OrderSpecifierGenerator { - public String schema() { - return this.schemaSql; + OrderSpecifier generate(Order order); } - @Override - public StandingInstructionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong("id"); - final Long accountDetailId = rs.getLong("accountDetailId"); - final String name = rs.getString("name"); - final Integer priority = JdbcSupport.getInteger(rs, "priority"); - EnumOptionData priorityEnum = AccountTransferEnumerations.standingInstructionPriority(priority); - - final Integer status = JdbcSupport.getInteger(rs, "status"); - EnumOptionData statusEnum = AccountTransferEnumerations.standingInstructionStatus(status); - final Integer instructionType = JdbcSupport.getInteger(rs, "instructionType"); - EnumOptionData instructionTypeEnum = AccountTransferEnumerations.standingInstructionType(instructionType); - final LocalDate validFrom = JdbcSupport.getLocalDate(rs, "validFrom"); - final LocalDate validTill = JdbcSupport.getLocalDate(rs, "validTill"); - final BigDecimal transferAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amount"); - final Integer recurrenceType = JdbcSupport.getInteger(rs, "recurrenceType"); - EnumOptionData recurrenceTypeEnum = AccountTransferEnumerations.recurrenceType(recurrenceType); - final Integer recurrenceFrequency = JdbcSupport.getInteger(rs, "recurrenceFrequency"); - - EnumOptionData recurrenceFrequencyEnum = null; - if (recurrenceFrequency != null) { - recurrenceFrequencyEnum = CommonEnumerations.termFrequencyType(recurrenceFrequency, "recurrence"); - } - final Integer recurrenceInterval = JdbcSupport.getInteger(rs, "recurrenceInterval"); - - MonthDay recurrenceOnMonthDay = null; - final Integer recurrenceOnDay = JdbcSupport.getInteger(rs, "recurrenceOnDay"); - final Integer recurrenceOnMonth = JdbcSupport.getInteger(rs, "recurrenceOnMonth"); - if (recurrenceOnDay != null) { - recurrenceOnMonthDay = MonthDay.now(DateUtils.getDateTimeZoneOfTenant()).withMonth(recurrenceOnMonth) - .withDayOfMonth(recurrenceOnDay); - } + private static final Map fieldToSpecifier = new HashMap<>(); + + static { + fieldToSpecifier.put("id", + order -> new OrderSpecifier<>(order, QAccountTransferStandingInstruction.accountTransferStandingInstruction.id)); + fieldToSpecifier.put("name", + order -> new OrderSpecifier<>(order, QAccountTransferStandingInstruction.accountTransferStandingInstruction.name)); + fieldToSpecifier.put("priority", + order -> new OrderSpecifier<>(order, QAccountTransferStandingInstruction.accountTransferStandingInstruction.priority)); + fieldToSpecifier.put("status", + order -> new OrderSpecifier<>(order, QAccountTransferStandingInstruction.accountTransferStandingInstruction.status)); + fieldToSpecifier.put("instructionType", order -> new OrderSpecifier<>(order, + QAccountTransferStandingInstruction.accountTransferStandingInstruction.instructionType)); + fieldToSpecifier.put("amount", + order -> new OrderSpecifier<>(order, QAccountTransferStandingInstruction.accountTransferStandingInstruction.amount)); + fieldToSpecifier.put("validFrom", + order -> new OrderSpecifier<>(order, QAccountTransferStandingInstruction.accountTransferStandingInstruction.validFrom)); + fieldToSpecifier.put("validTill", + order -> new OrderSpecifier<>(order, QAccountTransferStandingInstruction.accountTransferStandingInstruction.validTill)); + fieldToSpecifier.put("recurrenceType", order -> new OrderSpecifier<>(order, + QAccountTransferStandingInstruction.accountTransferStandingInstruction.recurrenceType)); + fieldToSpecifier.put("recurrenceFrequency", order -> new OrderSpecifier<>(order, + QAccountTransferStandingInstruction.accountTransferStandingInstruction.recurrenceFrequency)); + fieldToSpecifier.put("recurrenceInterval", order -> new OrderSpecifier<>(order, + QAccountTransferStandingInstruction.accountTransferStandingInstruction.recurrenceInterval)); + fieldToSpecifier.put("recurrenceOnDay", order -> new OrderSpecifier<>(order, + QAccountTransferStandingInstruction.accountTransferStandingInstruction.recurrenceOnDay)); + fieldToSpecifier.put("recurrenceOnMonth", order -> new OrderSpecifier<>(order, + QAccountTransferStandingInstruction.accountTransferStandingInstruction.recurrenceOnMonth)); + fieldToSpecifier.put("accountDetailId", + order -> new OrderSpecifier<>(order, QAccountTransferDetails.accountTransferDetails.id)); + fieldToSpecifier.put("transferType", + order -> new OrderSpecifier<>(order, QAccountTransferDetails.accountTransferDetails.transferType)); + fieldToSpecifier.put("fromOfficeId", order -> new OrderSpecifier<>(order, new QOffice("fromOffice").id)); + fieldToSpecifier.put("fromOfficeName", order -> new OrderSpecifier<>(order, new QOffice("fromOffice").name)); + fieldToSpecifier.put("toOfficeId", order -> new OrderSpecifier<>(order, new QOffice("toOffice").id)); + fieldToSpecifier.put("toOfficeName", order -> new OrderSpecifier<>(order, new QOffice("toOffice").name)); + fieldToSpecifier.put("fromClientId", order -> new OrderSpecifier<>(order, new QClient("fromClient").id)); + fieldToSpecifier.put("fromClientName", order -> new OrderSpecifier<>(order, new QClient("fromClient").displayName)); + fieldToSpecifier.put("toClientId", order -> new OrderSpecifier<>(order, new QClient("toClient").id)); + fieldToSpecifier.put("toClientName", order -> new OrderSpecifier<>(order, new QClient("toClient").displayName)); + fieldToSpecifier.put("fromSavingsAccountId", + order -> new OrderSpecifier<>(order, new QSavingsAccount("fromSavingsAccount").id)); + fieldToSpecifier.put("fromSavingsAccountNo", + order -> new OrderSpecifier<>(order, new QSavingsAccount("fromSavingsAccount").accountNumber)); + fieldToSpecifier.put("toSavingsAccountId", order -> new OrderSpecifier<>(order, new QSavingsAccount("toSavingsAccount").id)); + fieldToSpecifier.put("toSavingsAccountNo", + order -> new OrderSpecifier<>(order, new QSavingsAccount("toSavingsAccount").accountNumber)); + fieldToSpecifier.put("fromLoanAccountId", order -> new OrderSpecifier<>(order, new QLoan("fromLoan").id)); + fieldToSpecifier.put("fromLoanAccountNo", order -> new OrderSpecifier<>(order, new QLoan("fromLoan").accountNumber)); + fieldToSpecifier.put("toLoanAccountId", order -> new OrderSpecifier<>(order, new QLoan("toLoan").id)); + fieldToSpecifier.put("toLoanAccountNo", order -> new OrderSpecifier<>(order, new QLoan("toLoan").accountNumber)); + fieldToSpecifier.put("fromProductId", order -> new OrderSpecifier<>(order, new QSavingsProduct("fromSavingsProduct").id)); + fieldToSpecifier.put("fromProductName", order -> new OrderSpecifier<>(order, new QSavingsProduct("fromSavingsProduct").name)); + fieldToSpecifier.put("toProductId", order -> new OrderSpecifier<>(order, new QSavingsProduct("toSavingsProduct").id)); + fieldToSpecifier.put("fromLoanProductId", order -> new OrderSpecifier<>(order, new QLoanProduct("fromLoanProduct").id)); + fieldToSpecifier.put("fromLoanProductName", order -> new OrderSpecifier<>(order, new QLoanProduct("fromLoanProduct").name)); + fieldToSpecifier.put("toLoanProductId", order -> new OrderSpecifier<>(order, new QLoanProduct("toLoanProduct").id)); + fieldToSpecifier.put("toLoanProductName", order -> new OrderSpecifier<>(order, new QLoanProduct("toLoanProduct").name)); + } - final Integer transferType = rs.getInt("transferType"); - EnumOptionData transferTypeEnum = AccountTransferEnumerations.transferType(transferType); - - /* - * final String currencyCode = rs.getString("currencyCode"); final String currencyName = - * rs.getString("currencyName"); final String currencyNameCode = rs.getString("currencyNameCode"); final - * String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); final Integer currencyDigits = - * JdbcSupport.getInteger(rs, "currencyDigits"); final Integer inMultiplesOf = JdbcSupport.getInteger(rs, - * "inMultiplesOf"); final CurrencyData currency = new CurrencyData(currencyCode, currencyName, - * currencyDigits, inMultiplesOf, currencyDisplaySymbol, currencyNameCode); - */ - final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId"); - final String fromOfficeName = rs.getString("fromOfficeName"); - final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null); - - final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId"); - final String toOfficeName = rs.getString("toOfficeName"); - final OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null); - - final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId"); - final String fromClientName = rs.getString("fromClientName"); - final ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName); - - final Long toClientId = JdbcSupport.getLong(rs, "toClientId"); - final String toClientName = rs.getString("toClientName"); - final ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName); - - final Long fromSavingsAccountId = JdbcSupport.getLong(rs, "fromSavingsAccountId"); - final String fromSavingsAccountNo = rs.getString("fromSavingsAccountNo"); - final Long fromProductId = JdbcSupport.getLong(rs, "fromProductId"); - final String fromProductName = rs.getString("fromProductName"); - final Long fromLoanAccountId = JdbcSupport.getLong(rs, "fromLoanAccountId"); - final String fromLoanAccountNo = rs.getString("fromLoanAccountNo"); - final Long fromLoanProductId = JdbcSupport.getLong(rs, "fromLoanProductId"); - final String fromLoanProductName = rs.getString("fromLoanProductName"); - PortfolioAccountData fromAccount = null; - EnumOptionData fromAccountType = null; - if (fromSavingsAccountId != null) { - fromAccount = new PortfolioAccountData(fromSavingsAccountId, fromSavingsAccountNo, null, null, null, null, null, - fromProductId, fromProductName, null, null, null); - fromAccountType = accountType(PortfolioAccountType.SAVINGS); - } else if (fromLoanAccountId != null) { - fromAccount = new PortfolioAccountData(fromLoanAccountId, fromLoanAccountNo, null, null, null, null, null, - fromLoanProductId, fromLoanProductName, null, null, null); - fromAccountType = accountType(PortfolioAccountType.LOAN); + public static OrderSpecifier getSpecifier(final String fieldName, final Order order) { + final OrderSpecifierGenerator generator = fieldToSpecifier.get(fieldName); + if (generator != null) { + return generator.generate(order); + } else { + throw new IllegalArgumentException("Unknown order field name: " + fieldName); } + } + } - PortfolioAccountData toAccount = null; - EnumOptionData toAccountType = null; - final Long toSavingsAccountId = JdbcSupport.getLong(rs, "toSavingsAccountId"); - final String toSavingsAccountNo = rs.getString("toSavingsAccountNo"); - final Long toProductId = JdbcSupport.getLong(rs, "toProductId"); - final String toProductName = rs.getString("toProductName"); - final Long toLoanAccountId = JdbcSupport.getLong(rs, "toLoanAccountId"); - final String toLoanAccountNo = rs.getString("toLoanAccountNo"); - final Long toLoanProductId = JdbcSupport.getLong(rs, "toLoanProductId"); - final String toLoanProductName = rs.getString("toLoanProductName"); - - if (toSavingsAccountId != null) { - toAccount = new PortfolioAccountData(toSavingsAccountId, toSavingsAccountNo, null, null, null, null, null, toProductId, - toProductName, null, null, null); - toAccountType = accountType(PortfolioAccountType.SAVINGS); - } else if (toLoanAccountId != null) { - toAccount = new PortfolioAccountData(toLoanAccountId, toLoanAccountNo, null, null, null, null, null, toLoanProductId, - toLoanProductName, null, null, null); - toAccountType = accountType(PortfolioAccountType.LOAN); - } + private JPAQuery getStandingInstructionSelectQuery() { + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final QAccountTransferDetails qAccountTransferDetails = QAccountTransferDetails.accountTransferDetails; + final QOffice qFromOffice = new QOffice("qFromOffice"); + final QOffice qToOffice = new QOffice("qToOffice"); + final QClient qFromClient = new QClient("qFromClient"); + final QClient qToClient = new QClient("qToClient"); + final QSavingsAccount qFromSavingsAccount = new QSavingsAccount("qFromSavingsAccount"); + final QSavingsAccount qToSavingsAccount = new QSavingsAccount("qToSavingsAccount"); + final QSavingsProduct qFromSavingsProduct = new QSavingsProduct("qFromSavingsProduct"); + final QSavingsProduct qToSavingsProduct = new QSavingsProduct("qToSavingsProduct"); + final QLoan qFromLoan = new QLoan("qFromLoan"); + final QLoan qToLoan = new QLoan("qToLoan"); + final QLoanProduct qFromLoanProduct = new QLoanProduct("qFromLoanProduct"); + final QLoanProduct qToLoanProduct = new QLoanProduct("qToLoanProduct"); + + final JPAQuery query = new JPAQuery<>(entityManager); + query.select(qAccountTransferStandingInstruction.id, qAccountTransferStandingInstruction.name, + qAccountTransferStandingInstruction.priority, qAccountTransferStandingInstruction.status, + qAccountTransferStandingInstruction.instructionType, qAccountTransferStandingInstruction.amount, + qAccountTransferStandingInstruction.validFrom, qAccountTransferStandingInstruction.validTill, + qAccountTransferStandingInstruction.recurrenceType, qAccountTransferStandingInstruction.recurrenceFrequency, + qAccountTransferStandingInstruction.recurrenceInterval, qAccountTransferStandingInstruction.recurrenceOnDay, + qAccountTransferStandingInstruction.recurrenceOnMonth, qAccountTransferDetails.id.as("accountDetailId"), + qAccountTransferDetails.transferType, qFromOffice.id.as("fromOfficeId"), qFromOffice.name.as("fromOfficeName"), + qToOffice.id.as("toOfficeId"), qToOffice.name.as("toOfficeName"), qFromClient.id.as("fromClientId"), + qFromClient.displayName.as("fromClientName"), qToClient.id.as("toClientId"), qToClient.displayName.as("toClientName"), + qFromSavingsAccount.id.as("fromSavingsAccountId"), qFromSavingsAccount.accountNumber.as("fromSavingsAccountNo"), + qFromSavingsProduct.id.as("fromProductId"), qFromSavingsProduct.name.as("fromProductName"), + qFromLoan.id.as("fromLoanAccountId"), qFromLoan.accountNumber.as("fromLoanAccountNo"), + qFromLoanProduct.id.as("fromLoanProductId"), qFromLoanProduct.name.as("fromLoanProductName"), + qToSavingsAccount.id.as("toSavingsAccountId"), qToSavingsAccount.accountNumber.as("toSavingsAccountNo"), + qToSavingsProduct.id.as("toProductId"), qToSavingsProduct.name.as("toProductName"), qToLoan.id.as("toLoanAccountId"), + qToLoan.accountNumber.as("toLoanAccountNo"), qToLoanProduct.id.as("toLoanProductId"), + qToLoanProduct.name.as("toLoanProductName")).from(qAccountTransferStandingInstruction); + + addJoinsToStandingInstructionQuery(query); + return query; + } - return StandingInstructionData.instance(id, accountDetailId, name, fromOffice, toOffice, fromClient, toClient, fromAccountType, - fromAccount, toAccountType, toAccount, transferTypeEnum, priorityEnum, instructionTypeEnum, statusEnum, transferAmount, - validFrom, validTill, recurrenceTypeEnum, recurrenceFrequencyEnum, recurrenceInterval, recurrenceOnMonthDay); + private StandingInstructionData mapQueryResultToStandingInstructionDuesData(final Tuple queryResult) { + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final QAccountTransferDetails qAccountTransferDetails = QAccountTransferDetails.accountTransferDetails; + final QOffice qFromOffice = new QOffice("qFromOffice"); + final QOffice qToOffice = new QOffice("qToOffice"); + final QClient qFromClient = new QClient("qFromClient"); + final QClient qToClient = new QClient("qToClient"); + final QSavingsAccount qFromSavingsAccount = new QSavingsAccount("qFromSavingsAccount"); + final QSavingsAccount qToSavingsAccount = new QSavingsAccount("qToSavingsAccount"); + final QSavingsProduct qFromSavingsProduct = new QSavingsProduct("qFromSavingsProduct"); + final QSavingsProduct qToSavingsProduct = new QSavingsProduct("qToSavingsProduct"); + final QLoan qFromLoan = new QLoan("qFromLoan"); + final QLoan qToLoan = new QLoan("qToLoan"); + final QLoanProduct qFromLoanProduct = new QLoanProduct("qFromLoanProduct"); + final QLoanProduct qToLoanProduct = new QLoanProduct("qToLoanProduct"); + + EnumOptionData priorityEnum = AccountTransferEnumerations + .standingInstructionPriority(queryResult.get(qAccountTransferStandingInstruction.priority)); + EnumOptionData statusEnum = AccountTransferEnumerations + .standingInstructionStatus(queryResult.get(qAccountTransferStandingInstruction.status)); + EnumOptionData instructionTypeEnum = AccountTransferEnumerations + .standingInstructionType(queryResult.get(qAccountTransferStandingInstruction.instructionType)); + + final LocalDate validFrom = queryResult.get(qAccountTransferStandingInstruction.validFrom); + final LocalDate validTill = queryResult.get(qAccountTransferStandingInstruction.validTill); + final BigDecimal transferAmount = JdbcSupport.defaultToNullIfZero(queryResult.get(qAccountTransferStandingInstruction.amount)); + EnumOptionData recurrenceTypeEnum = AccountTransferEnumerations + .recurrenceType(queryResult.get(qAccountTransferStandingInstruction.recurrenceType)); + final Integer recurrenceFrequency = queryResult.get(qAccountTransferStandingInstruction.recurrenceFrequency); + + EnumOptionData recurrenceFrequencyEnum = null; + if (recurrenceFrequency != null) { + recurrenceFrequencyEnum = CommonEnumerations.termFrequencyType(recurrenceFrequency, "recurrence"); } - } - private static final class StandingInstructionLoanDuesMapper implements RowMapper { - - private final String schemaSql; - - StandingInstructionLoanDuesMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - - sqlBuilder.append("max(ls.duedate) as dueDate,sum(ls.principal_amount) as principalAmount,"); - sqlBuilder.append("sum(ls.principal_completed_derived) as principalCompleted,"); - sqlBuilder.append("sum(ls.principal_writtenoff_derived) as principalWrittenOff,"); - sqlBuilder.append("sum(ls.interest_amount) as interestAmount,"); - sqlBuilder.append("sum(ls.interest_completed_derived) as interestCompleted,"); - sqlBuilder.append("sum(ls.interest_writtenoff_derived) as interestWrittenOff,"); - sqlBuilder.append("sum(ls.interest_waived_derived) as interestWaived,"); - sqlBuilder.append("sum(ls.penalty_charges_amount) as penalityAmount,"); - sqlBuilder.append("sum(ls.penalty_charges_completed_derived) as penalityCompleted,"); - sqlBuilder.append("sum(ls.penalty_charges_writtenoff_derived)as penaltyWrittenOff,"); - sqlBuilder.append("sum(ls.penalty_charges_waived_derived) as penaltyWaived,"); - sqlBuilder.append("sum(ls.fee_charges_amount) as feeAmount,"); - sqlBuilder.append("sum(ls.fee_charges_completed_derived) as feecompleted,"); - sqlBuilder.append("sum(ls.fee_charges_writtenoff_derived) as feeWrittenOff,"); - sqlBuilder.append("sum(ls.fee_charges_waived_derived) as feeWaived "); - sqlBuilder.append("from m_loan_repayment_schedule ls "); - sqlBuilder.append(" join m_loan ml on ml.id = ls.loan_id "); - - this.schemaSql = sqlBuilder.toString(); + MonthDay recurrenceOnMonthDay = null; + final Integer recurrenceOnDay = queryResult.get(qAccountTransferStandingInstruction.recurrenceOnDay); + final Integer recurrenceOnMonth = queryResult.get(qAccountTransferStandingInstruction.recurrenceOnMonth); + if (recurrenceOnDay != null && recurrenceOnMonth != null) { + recurrenceOnMonthDay = MonthDay.now(DateUtils.getDateTimeZoneOfTenant()).withMonth(recurrenceOnMonth) + .withDayOfMonth(recurrenceOnDay); } - public String schema() { - return this.schemaSql; + EnumOptionData transferTypeEnum = AccountTransferEnumerations.transferType(queryResult.get(qAccountTransferDetails.transferType)); + + final Long fromOfficeId = queryResult.get(qFromOffice.id.as("fromOfficeId")); + final String fromOfficeName = queryResult.get(qFromOffice.name.as("fromOfficeName")); + final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null); + + final Long toOfficeId = queryResult.get(qToOffice.id.as("toOfficeId")); + final String toOfficeName = queryResult.get(qToOffice.name.as("toOfficeName")); + final OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null); + + final Long fromClientId = queryResult.get(qFromClient.id.as("fromClientId")); + final String fromClientName = queryResult.get(qFromClient.displayName.as("fromClientName")); + final ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName); + + final Long toClientId = queryResult.get(qToClient.id.as("toClientId")); + final String toClientName = queryResult.get(qToClient.displayName.as("toClientName")); + final ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName); + + final Long fromSavingsAccountId = queryResult.get(qFromSavingsAccount.id.as("fromSavingsAccountId")); + final String fromSavingsAccountNo = queryResult.get(qFromSavingsAccount.accountNumber.as("fromSavingsAccountNo")); + final Long fromProductId = queryResult.get(qFromSavingsProduct.id.as("fromProductId")); + final String fromProductName = queryResult.get(qFromSavingsProduct.name.as("fromProductName")); + final Long fromLoanAccountId = queryResult.get(qFromLoan.id.as("fromLoanAccountId")); + final String fromLoanAccountNo = queryResult.get(qFromLoan.accountNumber.as("fromLoanAccountNo")); + final Long fromLoanProductId = queryResult.get(qFromLoanProduct.id.as("fromLoanProductId")); + final String fromLoanProductName = queryResult.get(qFromLoanProduct.name.as("fromLoanProductName")); + PortfolioAccountData fromAccount = null; + EnumOptionData fromAccountType = null; + + if (fromSavingsAccountId != null) { + fromAccount = new PortfolioAccountData(fromSavingsAccountId, fromSavingsAccountNo, null, null, null, null, null, fromProductId, + fromProductName, null, null, null); + fromAccountType = accountType(PortfolioAccountType.SAVINGS); + } else if (fromLoanAccountId != null) { + fromAccount = new PortfolioAccountData(fromLoanAccountId, fromLoanAccountNo, null, null, null, null, null, fromLoanProductId, + fromLoanProductName, null, null, null); + fromAccountType = accountType(PortfolioAccountType.LOAN); } - @Override - public StandingInstructionDuesData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate"); - final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalAmount"); - final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalCompleted"); - final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff"); - final BigDecimal principalOutstanding = principalDue.subtract(principalPaid).subtract(principalWrittenOff); - - final BigDecimal interestExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestAmount"); - final BigDecimal interestPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestCompleted"); - final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff"); - final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived"); - final BigDecimal interestActualDue = interestExpectedDue.subtract(interestWaived).subtract(interestWrittenOff); - final BigDecimal interestOutstanding = interestActualDue.subtract(interestPaid); - - final BigDecimal penaltyChargesExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalityAmount"); - final BigDecimal penaltyChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penalityCompleted"); - final BigDecimal penaltyChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyWrittenOff"); - final BigDecimal penaltyChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyWaived"); - final BigDecimal penaltyChargesActualDue = penaltyChargesExpectedDue.subtract(penaltyChargesWaived) - .subtract(penaltyChargesWrittenOff); - final BigDecimal penaltyChargesOutstanding = penaltyChargesActualDue.subtract(penaltyChargesPaid); - - final BigDecimal feeChargesExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeAmount"); - final BigDecimal feeChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feecompleted"); - final BigDecimal feeChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeWrittenOff"); - final BigDecimal feeChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeWaived"); - final BigDecimal feeChargesActualDue = feeChargesExpectedDue.subtract(feeChargesWaived).subtract(feeChargesWrittenOff); - final BigDecimal feeChargesOutstanding = feeChargesActualDue.subtract(feeChargesPaid); - - final BigDecimal totalOutstanding = principalOutstanding.add(interestOutstanding).add(feeChargesOutstanding) - .add(penaltyChargesOutstanding); - - return new StandingInstructionDuesData(dueDate, totalOutstanding); + PortfolioAccountData toAccount = null; + EnumOptionData toAccountType = null; + final Long toSavingsAccountId = queryResult.get(qToSavingsAccount.id.as("toSavingsAccountId")); + final String toSavingsAccountNo = queryResult.get(qToSavingsAccount.accountNumber.as("toSavingsAccountNo")); + final Long toProductId = queryResult.get(qToSavingsProduct.id.as("toProductId")); + final String toProductName = queryResult.get(qToSavingsProduct.name.as("toProductName")); + final Long toLoanAccountId = queryResult.get(qToLoan.id.as("toLoanAccountId")); + final String toLoanAccountNo = queryResult.get(qToLoan.accountNumber.as("toLoanAccountNo")); + final Long toLoanProductId = queryResult.get(qToLoanProduct.id.as("toLoanProductId")); + final String toLoanProductName = queryResult.get(qToLoanProduct.name.as("toLoanProductName")); + + if (toSavingsAccountId != null) { + toAccount = new PortfolioAccountData(toSavingsAccountId, toSavingsAccountNo, null, null, null, null, null, toProductId, + toProductName, null, null, null); + toAccountType = accountType(PortfolioAccountType.SAVINGS); + } else if (toLoanAccountId != null) { + toAccount = new PortfolioAccountData(toLoanAccountId, toLoanAccountNo, null, null, null, null, null, toLoanProductId, + toLoanProductName, null, null, null); + toAccountType = accountType(PortfolioAccountType.LOAN); } + + return StandingInstructionData.instance(queryResult.get(qAccountTransferStandingInstruction.id), + queryResult.get(qAccountTransferDetails.id.as("accountDetailId")), + queryResult.get(qAccountTransferStandingInstruction.name), fromOffice, toOffice, fromClient, toClient, fromAccountType, + fromAccount, toAccountType, toAccount, transferTypeEnum, priorityEnum, instructionTypeEnum, statusEnum, transferAmount, + validFrom, validTill, recurrenceTypeEnum, recurrenceFrequencyEnum, + queryResult.get(qAccountTransferStandingInstruction.recurrenceInterval), recurrenceOnMonthDay); + } + + private List mapQueryResultToStandingInstructionDuesDataList(final List queryResult) { + return queryResult.stream().map(this::mapQueryResultToStandingInstructionDuesData).collect(toList()); } + private JPAQuery getStandingInstructionDuesSelectQuery() { + final QLoanRepaymentScheduleInstallment qLoanRepaymentSchedule = QLoanRepaymentScheduleInstallment.loanRepaymentScheduleInstallment; + final QLoan qLoan = QLoan.loan; + + final JPAQuery query = new JPAQuery<>(entityManager); + + query.select(qLoanRepaymentSchedule.dueDate.max().as("dueDate"), + qLoanRepaymentSchedule.principal.sumBigDecimal().as("principalAmount"), + qLoanRepaymentSchedule.principalCompleted.sumBigDecimal().as("principalCompleted"), + qLoanRepaymentSchedule.principalWrittenOff.sumBigDecimal().as("principalWrittenOff"), + qLoanRepaymentSchedule.interestCharged.sumBigDecimal().as("interestAmount"), + qLoanRepaymentSchedule.interestPaid.sumBigDecimal().as("interestCompleted"), + qLoanRepaymentSchedule.interestWrittenOff.sumBigDecimal().as("interestWrittenOff"), + qLoanRepaymentSchedule.interestWaived.sumBigDecimal().as("interestWaived"), + qLoanRepaymentSchedule.penaltyCharges.sumBigDecimal().as("penalityAmount"), + qLoanRepaymentSchedule.penaltyChargesPaid.sumBigDecimal().as("penalityCompleted"), + qLoanRepaymentSchedule.penaltyChargesWrittenOff.sumBigDecimal().as("penaltyWrittenOff"), + qLoanRepaymentSchedule.penaltyChargesWaived.sumBigDecimal().as("penaltyWaived"), + qLoanRepaymentSchedule.feeChargesCharged.sumBigDecimal().as("feeAmount"), + qLoanRepaymentSchedule.feeChargesPaid.sumBigDecimal().as("feecompleted"), + qLoanRepaymentSchedule.feeChargesWrittenOff.sumBigDecimal().as("feeWrittenOff"), + qLoanRepaymentSchedule.feeChargesWaived.sumBigDecimal().as("feeWaived")).from(qLoanRepaymentSchedule).join(qLoan) + .on(qLoan.id.eq(qLoanRepaymentSchedule.loan.id)); + + return query; + } + + private void addJoinsToStandingInstructionQuery(final JPAQuery selectFromQuery) { + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final QAccountTransferDetails qAccountTransferDetails = QAccountTransferDetails.accountTransferDetails; + final QOffice qFromOffice = new QOffice("qFromOffice"); + final QOffice qToOffice = new QOffice("qToOffice"); + final QClient qFromClient = new QClient("qFromClient"); + final QClient qToClient = new QClient("qToClient"); + final QSavingsAccount qFromSavingsAccount = new QSavingsAccount("qFromSavingsAccount"); + final QSavingsAccount qToSavingsAccount = new QSavingsAccount("qToSavingsAccount"); + final QSavingsProduct qFromSavingsProduct = new QSavingsProduct("qFromSavingsProduct"); + final QSavingsProduct qToSavingsProduct = new QSavingsProduct("qToSavingsProduct"); + final QLoan qFromLoan = new QLoan("qFromLoan"); + final QLoan qToLoan = new QLoan("qToLoan"); + final QLoanProduct qFromLoanProduct = new QLoanProduct("qFromLoanProduct"); + final QLoanProduct qToLoanProduct = new QLoanProduct("qToLoanProduct"); + + selectFromQuery.join(qAccountTransferStandingInstruction.accountTransferDetails, qAccountTransferDetails) + .on(qAccountTransferDetails.id.eq(qAccountTransferStandingInstruction.accountTransferDetails.id)) + .join(qAccountTransferDetails.fromOffice, qFromOffice).on(qFromOffice.id.eq(qAccountTransferDetails.fromOffice.id)) + .join(qAccountTransferDetails.toOffice, qToOffice).on(qToOffice.id.eq(qAccountTransferDetails.toOffice.id)) + .join(qAccountTransferDetails.fromClient, qFromClient).on(qFromClient.id.eq(qAccountTransferDetails.fromClient.id)) + .join(qAccountTransferDetails.toClient, qToClient).on(qToClient.id.eq(qAccountTransferDetails.toClient.id)) + .leftJoin(qAccountTransferDetails.fromSavingsAccount, qFromSavingsAccount) + .on(qFromSavingsAccount.id.eq(qAccountTransferDetails.fromSavingsAccount.id)) + .leftJoin(qFromSavingsAccount.product, qFromSavingsProduct).on(qFromSavingsProduct.id.eq(qFromSavingsAccount.product.id)) + .leftJoin(qAccountTransferDetails.fromLoanAccount, qFromLoan) + .on(qFromLoan.id.eq(qAccountTransferDetails.fromLoanAccount.id)).leftJoin(qFromLoan.loanProduct, qFromLoanProduct) + .on(qFromLoanProduct.id.eq(qFromLoan.loanProduct.id)).leftJoin(qAccountTransferDetails.toSavingsAccount, qToSavingsAccount) + .on(qToSavingsAccount.id.eq(qAccountTransferDetails.toSavingsAccount.id)) + .leftJoin(qToSavingsAccount.product, qToSavingsProduct).on(qToSavingsProduct.id.eq(qToSavingsAccount.product.id)) + .leftJoin(qAccountTransferDetails.toLoanAccount, qToLoan).on(qToLoan.id.eq(qAccountTransferDetails.toLoanAccount.id)) + .leftJoin(qToLoan.loanProduct, qToLoanProduct).on(qToLoanProduct.id.eq(qToLoan.loanProduct.id)); + } + + private JPAQuery getStandingInstructionCountQuery() { + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final JPAQuery query = new JPAQuery<>(entityManager); + + query.select(ONE.count()).from(qAccountTransferStandingInstruction); + addJoinsToStandingInstructionQuery(query); + return query; + } + + private BooleanExpression eq(final SimpleExpression expression, final T value) { + return value == null ? expression.isNull() : expression.eq(value); + } + + private BooleanExpression addCondition(final BooleanExpression whereClause, final BooleanExpression condition) { + if (whereClause == null) { + return condition; + } else { + return whereClause.and(condition); + } + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java index 90a827854cb..b3452a259f7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/starter/AccountConfiguration.java @@ -18,15 +18,15 @@ */ package org.apache.fineract.portfolio.account.starter; +import jakarta.persistence.EntityManager; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.core.service.PaginationHelper; -import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; -import org.apache.fineract.infrastructure.security.utils.ColumnValidator; import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; import org.apache.fineract.portfolio.account.data.AccountTransfersDataValidator; import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator; +import org.apache.fineract.portfolio.account.domain.AccountAssociationsCustomRepository; import org.apache.fineract.portfolio.account.domain.AccountTransferAssembler; import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository; import org.apache.fineract.portfolio.account.domain.AccountTransferRepository; @@ -58,25 +58,24 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.jdbc.core.JdbcTemplate; @Configuration public class AccountConfiguration { @Bean @ConditionalOnMissingBean(AccountAssociationsReadPlatformService.class) - public AccountAssociationsReadPlatformService accountAssociationsReadPlatformService(JdbcTemplate jdbcTemplate) { - return new AccountAssociationsReadPlatformServiceImpl(jdbcTemplate); + public AccountAssociationsReadPlatformService accountAssociationsReadPlatformService(EntityManager entityManager, + final AccountAssociationsCustomRepository accountAssociationsCustomRepository) { + return new AccountAssociationsReadPlatformServiceImpl(entityManager, accountAssociationsCustomRepository); } @Bean @ConditionalOnMissingBean(AccountTransfersReadPlatformService.class) - public AccountTransfersReadPlatformService accountTransfersReadPlatformService(JdbcTemplate jdbcTemplate, - ClientReadPlatformService clientReadPlatformService, OfficeReadPlatformService officeReadPlatformService, - PortfolioAccountReadPlatformService portfolioAccountReadPlatformService, ColumnValidator columnValidator, - DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) { - return new AccountTransfersReadPlatformServiceImpl(jdbcTemplate, clientReadPlatformService, officeReadPlatformService, - portfolioAccountReadPlatformService, columnValidator, sqlGenerator, paginationHelper); + public AccountTransfersReadPlatformService accountTransfersReadPlatformService(ClientReadPlatformService clientReadPlatformService, + OfficeReadPlatformService officeReadPlatformService, PortfolioAccountReadPlatformService portfolioAccountReadPlatformService, + PaginationHelper paginationHelper, EntityManager entityManager) { + return new AccountTransfersReadPlatformServiceImpl(clientReadPlatformService, officeReadPlatformService, + portfolioAccountReadPlatformService, paginationHelper, entityManager); } @Bean @@ -88,36 +87,34 @@ public AccountTransfersWritePlatformService accountTransfersWritePlatformService LoanAccountDomainService loanAccountDomainService, SavingsAccountWritePlatformService savingsAccountWritePlatformService, AccountTransferDetailRepository accountTransferDetailRepository, LoanReadPlatformService loanReadPlatformService, GSIMRepositoy gsimRepository, ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory, - FineractProperties fineractProperties) { + FineractProperties fineractProperties, EntityManager entityManager) { return new AccountTransfersWritePlatformServiceImpl(accountTransfersDataValidator, accountTransferAssembler, accountTransferRepository, savingsAccountAssembler, savingsAccountDomainService, loanAccountAssembler, loanAccountDomainService, savingsAccountWritePlatformService, accountTransferDetailRepository, loanReadPlatformService, - gsimRepository, configurationDomainService, externalIdFactory, fineractProperties); + gsimRepository, configurationDomainService, externalIdFactory, fineractProperties, entityManager); } @Bean @ConditionalOnMissingBean(PortfolioAccountReadPlatformService.class) - public PortfolioAccountReadPlatformService portfolioAccountReadPlatformService(JdbcTemplate jdbcTemplate, - DatabaseSpecificSQLGenerator sqlGenerator) { - return new PortfolioAccountReadPlatformServiceImpl(jdbcTemplate, sqlGenerator); + public PortfolioAccountReadPlatformService portfolioAccountReadPlatformService(EntityManager entityManager) { + return new PortfolioAccountReadPlatformServiceImpl(entityManager); } @Bean @ConditionalOnMissingBean(StandingInstructionHistoryReadPlatformService.class) - public StandingInstructionHistoryReadPlatformService standingInstructionHistoryReadPlatformService(JdbcTemplate jdbcTemplate, - ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) { - return new StandingInstructionHistoryReadPlatformServiceImpl(jdbcTemplate, columnValidator, sqlGenerator, paginationHelper); + public StandingInstructionHistoryReadPlatformService standingInstructionHistoryReadPlatformService(PaginationHelper paginationHelper, + EntityManager entityManager) { + return new StandingInstructionHistoryReadPlatformServiceImpl(paginationHelper, entityManager); } @Bean @ConditionalOnMissingBean(StandingInstructionReadPlatformService.class) - public StandingInstructionReadPlatformService standingInstructionReadPlatformService(JdbcTemplate jdbcTemplate, + public StandingInstructionReadPlatformService standingInstructionReadPlatformService( ClientReadPlatformService clientReadPlatformService, OfficeReadPlatformService officeReadPlatformService, PortfolioAccountReadPlatformService portfolioAccountReadPlatformService, - DropdownReadPlatformService dropdownReadPlatformService, ColumnValidator columnValidator, - DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) { - return new StandingInstructionReadPlatformServiceImpl(jdbcTemplate, clientReadPlatformService, officeReadPlatformService, - portfolioAccountReadPlatformService, dropdownReadPlatformService, columnValidator, sqlGenerator, paginationHelper); + DropdownReadPlatformService dropdownReadPlatformService, PaginationHelper paginationHelper, EntityManager entityManager) { + return new StandingInstructionReadPlatformServiceImpl(clientReadPlatformService, officeReadPlatformService, + portfolioAccountReadPlatformService, dropdownReadPlatformService, paginationHelper, entityManager); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index b44af4d2d6b..3950a485e0a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.loanaccount.domain; +import com.querydsl.jpa.impl.JPAQuery; +import jakarta.persistence.EntityManager; import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; @@ -79,6 +81,8 @@ import org.apache.fineract.portfolio.account.domain.AccountTransferRepository; import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction; import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction; +import org.apache.fineract.portfolio.account.domain.QAccountTransferStandingInstruction; +import org.apache.fineract.portfolio.account.domain.QAccountTransferTransaction; import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository; import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; import org.apache.fineract.portfolio.accountdetails.domain.AccountType; @@ -141,6 +145,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper; private final DelinquencyReadPlatformService delinquencyReadPlatformService; + private final EntityManager entityManager; @Transactional @Override @@ -703,7 +708,19 @@ private void generateLoanScheduleAccrualData(final LocalDate accruedTill, } private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { - final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); + final QAccountTransferTransaction qAccountTransferTransaction = QAccountTransferTransaction.accountTransferTransaction; + final JPAQuery query = new JPAQuery<>(entityManager); + final AccountTransferTransaction transferTransaction; + if (loanTransactionId == null) { + transferTransaction = query.select(qAccountTransferTransaction).from(qAccountTransferTransaction) + .where(qAccountTransferTransaction.toLoanTransaction.id.isNull().and(qAccountTransferTransaction.reversed.isFalse())) + .fetchOne(); + } else { + transferTransaction = query.select(qAccountTransferTransaction).from(qAccountTransferTransaction) + .where(qAccountTransferTransaction.toLoanTransaction.id.eq(loanTransactionId) + .and(qAccountTransferTransaction.reversed.isFalse())) + .fetchOne(); + } if (transferTransaction != null) { transferTransaction.updateToLoanTransaction(newLoanTransaction); this.accountTransferRepository.save(transferTransaction); @@ -910,8 +927,14 @@ public LoanTransaction foreCloseLoan(Loan loan, final LocalDate foreClosureDate, public void disableStandingInstructionsLinkedToClosedLoan(Loan loan) { if ((loan != null) && (loan.getStatus() != null) && loan.getStatus().isClosed()) { final Integer standingInstructionStatus = StandingInstructionStatus.ACTIVE.getValue(); - final Collection accountTransferStandingInstructions = this.standingInstructionRepository - .findByLoanAccountAndStatus(loan, standingInstructionStatus); + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final JPAQuery query = new JPAQuery<>(entityManager); + final Collection accountTransferStandingInstructions = query + .select(qAccountTransferStandingInstruction).from(qAccountTransferStandingInstruction).where( + qAccountTransferStandingInstruction.status.eq(standingInstructionStatus) + .and(qAccountTransferStandingInstruction.accountTransferDetails.toLoanAccount.eq(loan) + .or(qAccountTransferStandingInstruction.accountTransferDetails.fromLoanAccount.eq(loan)))) + .fetch(); if (!accountTransferStandingInstructions.isEmpty()) { for (AccountTransferStandingInstruction accountTransferStandingInstruction : accountTransferStandingInstructions) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 96ab3a928f2..5fe11b79864 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -21,6 +21,8 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.querydsl.jpa.impl.JPAQuery; +import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceException; import java.math.BigDecimal; import java.time.LocalDate; @@ -76,6 +78,7 @@ import org.apache.fineract.portfolio.account.domain.AccountAssociationType; import org.apache.fineract.portfolio.account.domain.AccountAssociations; import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository; +import org.apache.fineract.portfolio.account.domain.QAccountAssociations; import org.apache.fineract.portfolio.accountdetails.domain.AccountType; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType; @@ -197,6 +200,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final GSIMReadPlatformService gsimReadPlatformService; private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; private final LoanProductDataValidator loanProductDataValidator; + private final EntityManager entityManager; @Transactional @Override @@ -1100,8 +1104,21 @@ public CommandProcessingResult modifyApplication(final Long loanId, final JsonCo final String linkAccountIdParamName = "linkAccountId"; final boolean backdatedTxnsAllowedTill = false; final Long savingsAccountId = command.longValueOfParameterNamed(linkAccountIdParamName); - AccountAssociations accountAssociations = this.accountAssociationsRepository.findByLoanIdAndType(loanId, - AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()); + final QAccountAssociations qAccountAssociations = QAccountAssociations.accountAssociations; + final JPAQuery query = new JPAQuery<>(entityManager); + + AccountAssociations accountAssociations; + if (loanId == null) { + accountAssociations = query.select(qAccountAssociations).from(qAccountAssociations) + .where(qAccountAssociations.loanAccount.id.isNull() + .and(qAccountAssociations.associationType.eq(AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()))) + .fetchOne(); + } else { + accountAssociations = query.select(qAccountAssociations).from(qAccountAssociations) + .where(qAccountAssociations.loanAccount.id.eq(loanId) + .and(qAccountAssociations.associationType.eq(AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()))) + .fetchOne(); + } boolean isLinkedAccPresent = false; if (savingsAccountId == null) { if (accountAssociations != null) { @@ -1228,8 +1245,21 @@ public CommandProcessingResult deleteApplication(final Long loanId) { final List relatedNotes = this.noteRepository.findByLoanId(loan.getId()); this.noteRepository.deleteAllInBatch(relatedNotes); - final AccountAssociations accountAssociations = this.accountAssociationsRepository.findByLoanIdAndType(loanId, - AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()); + final QAccountAssociations qAccountAssociations = QAccountAssociations.accountAssociations; + final JPAQuery query = new JPAQuery<>(entityManager); + + final AccountAssociations accountAssociations; + if (loanId == null) { + accountAssociations = query.select(qAccountAssociations).from(qAccountAssociations) + .where(qAccountAssociations.loanAccount.id.isNull() + .and(qAccountAssociations.associationType.eq(AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()))) + .fetchOne(); + } else { + accountAssociations = query.select(qAccountAssociations).from(qAccountAssociations) + .where(qAccountAssociations.loanAccount.id.eq(loanId) + .and(qAccountAssociations.associationType.eq(AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()))) + .fetchOne(); + } if (accountAssociations != null) { this.accountAssociationsRepository.delete(accountAssociations); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 30178a248c1..f3187341f0f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -22,8 +22,10 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.querydsl.jpa.impl.JPAQuery; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.github.resilience4j.retry.annotation.Retry; +import jakarta.persistence.EntityManager; import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @@ -111,6 +113,7 @@ import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType; import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction; import org.apache.fineract.portfolio.account.domain.AccountTransferType; +import org.apache.fineract.portfolio.account.domain.QAccountAssociations; import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority; import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; import org.apache.fineract.portfolio.account.domain.StandingInstructionType; @@ -249,6 +252,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; private final ErrorHandler errorHandler; private final LoanDownPaymentHandlerService loanDownPaymentHandlerService; + private final EntityManager entityManager; @Transactional @Override @@ -607,8 +611,13 @@ private void createAndSaveLoanScheduleArchive(final Loan loan, ScheduleGenerator private void createStandingInstruction(Loan loan) { if (loan.shouldCreateStandingInstructionAtDisbursement()) { - AccountAssociations accountAssociations = this.accountAssociationRepository.findByLoanIdAndType(loan.getId(), - AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()); + final QAccountAssociations qAccountAssociations = QAccountAssociations.accountAssociations; + final JPAQuery query = new JPAQuery<>(entityManager); + + final AccountAssociations accountAssociations = query.select(qAccountAssociations).from(qAccountAssociations) + .where(qAccountAssociations.loanAccount.id.eq(loan.getId()) + .and(qAccountAssociations.associationType.eq(AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()))) + .fetchOne(); if (accountAssociations != null) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java index 501e9858296..0564967851b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.starter; +import jakarta.persistence.EntityManager; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.cob.service.LoanAccountLockService; import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper; @@ -231,7 +232,8 @@ public LoanApplicationWritePlatformService loanApplicationWritePlatformService(P RateAssembler rateAssembler, GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService, GLIMAccountInfoRepository glimRepository, LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService, - LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanProductDataValidator loanProductDataValidator) { + LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanProductDataValidator loanProductDataValidator, + EntityManager entityManager) { return new LoanApplicationWritePlatformServiceJpaRepositoryImpl(context, fromJsonHelper, loanApplicationTransitionApiJsonValidator, loanProductCommandFromApiJsonDeserializer, fromApiJsonDeserializer, loanRepositoryWrapper, noteRepository, calculationPlatformService, loanAssembler, clientRepository, loanProductRepository, loanChargeAssembler, @@ -241,7 +243,8 @@ public LoanApplicationWritePlatformService loanApplicationWritePlatformService(P configurationDomainService, loanScheduleAssembler, loanUtilService, calendarReadPlatformService, entityDatatableChecksWritePlatformService, globalConfigurationRepository, entityMappingRepository, fineractEntityRelationRepository, loanProductReadPlatformService, rateAssembler, glimAccountInfoWritePlatformService, - glimRepository, loanRepository, gsimReadPlatformService, defaultLoanLifecycleStateMachine, loanProductDataValidator); + glimRepository, loanRepository, gsimReadPlatformService, defaultLoanLifecycleStateMachine, loanProductDataValidator, + entityManager); } @Bean @@ -403,7 +406,7 @@ public LoanWritePlatformService loanWritePlatformService(PlatformSecurityContext LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccountLockService loanAccountLockService, ExternalIdFactory externalIdFactory, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService, LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService, ErrorHandler errorHandler, - LoanDownPaymentHandlerService loanDownPaymentHandlerService) { + LoanDownPaymentHandlerService loanDownPaymentHandlerService, EntityManager entityManager) { return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanEventApiJsonValidator, loanUpdateCommandFromApiJsonDeserializer, loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository, loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository, @@ -416,7 +419,7 @@ public LoanWritePlatformService loanWritePlatformService(PlatformSecurityContext cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler, postDatedChecksRepository, loanDisbursementDetailsRepository, loanRepaymentScheduleInstallmentRepository, defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService, - loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService); + loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService, entityManager); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index a46c35b974f..98a1e220ffe 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -23,6 +23,8 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.recurringFrequencyTypeParamName; import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName; +import com.querydsl.jpa.impl.JPAQuery; +import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceException; import java.math.MathContext; import java.time.LocalDate; @@ -58,6 +60,7 @@ import org.apache.fineract.portfolio.account.domain.AccountAssociationType; import org.apache.fineract.portfolio.account.domain.AccountAssociations; import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository; +import org.apache.fineract.portfolio.account.domain.QAccountAssociations; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType; import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType; @@ -121,6 +124,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl private final ConfigurationDomainService configurationDomainService; private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository; private final BusinessEventNotifierService businessEventNotifierService; + private final EntityManager entityManager; /* * Guaranteed to throw an exception no matter what the data integrity issue is. @@ -361,8 +365,22 @@ public CommandProcessingResult modifyFDApplication(final Long accountId, final J // Save linked account information final Long savingsAccountId = command.longValueOfParameterNamed(DepositsApiConstants.linkedAccountParamName); - AccountAssociations accountAssociations = this.accountAssociationsRepository.findBySavingsIdAndType(accountId, - AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()); + + final QAccountAssociations qAccountAssociations = QAccountAssociations.accountAssociations; + final JPAQuery query = new JPAQuery<>(entityManager); + + AccountAssociations accountAssociations; + if (accountId == null) { + accountAssociations = query.select(qAccountAssociations).from(qAccountAssociations) + .where(qAccountAssociations.savingsAccount.id.isNull() + .and(qAccountAssociations.associationType.eq(AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()))) + .fetchOne(); + } else { + accountAssociations = query.select(qAccountAssociations).from(qAccountAssociations) + .where(qAccountAssociations.savingsAccount.id.eq(accountId) + .and(qAccountAssociations.associationType.eq(AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()))) + .fetchOne(); + } if (savingsAccountId == null) { if (accountAssociations != null) { if (this.fromJsonHelper.parameterExists(DepositsApiConstants.linkedAccountParamName, command.parsedJson())) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 35061831131..13da17499f7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -31,7 +31,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.querydsl.jpa.impl.JPAQuery; import io.github.resilience4j.retry.annotation.Retry; +import jakarta.persistence.EntityManager; import java.math.BigDecimal; import java.math.MathContext; import java.time.LocalDate; @@ -81,6 +83,7 @@ import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction; +import org.apache.fineract.portfolio.account.domain.QAccountTransferStandingInstruction; import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository; import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService; @@ -166,6 +169,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi private final GSIMRepositoy gsimRepository; private final SavingsAccountInterestPostingService savingsAccountInterestPostingService; private final ErrorHandler errorHandler; + private final EntityManager entityManager; @Transactional @Override @@ -1673,8 +1677,14 @@ private AppUser getAppUserIfPresent() { private void disableStandingInstructionsLinkedToClosedSavings(final SavingsAccount savingsAccount) { if (savingsAccount != null && savingsAccount.isClosed()) { final Integer standingInstructionStatus = StandingInstructionStatus.ACTIVE.getValue(); - final Collection accountTransferStandingInstructions = this.standingInstructionRepository - .findBySavingsAccountAndStatus(savingsAccount, standingInstructionStatus); + final QAccountTransferStandingInstruction qAccountTransferStandingInstruction = QAccountTransferStandingInstruction.accountTransferStandingInstruction; + final JPAQuery query = new JPAQuery<>(entityManager); + final Collection accountTransferStandingInstructions = query + .select(qAccountTransferStandingInstruction).from(qAccountTransferStandingInstruction) + .where(qAccountTransferStandingInstruction.status.eq(standingInstructionStatus) + .and(qAccountTransferStandingInstruction.accountTransferDetails.toSavingsAccount.eq(savingsAccount) + .or(qAccountTransferStandingInstruction.accountTransferDetails.fromSavingsAccount.eq(savingsAccount)))) + .fetch(); if (!accountTransferStandingInstructions.isEmpty()) { for (AccountTransferStandingInstruction accountTransferStandingInstruction : accountTransferStandingInstructions) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java index 1d04addb61a..0e9484598c0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.savings.starter; +import jakarta.persistence.EntityManager; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.apache.fineract.commands.service.CommandProcessingService; @@ -254,13 +255,13 @@ public DepositApplicationProcessWritePlatformService depositApplicationProcessWr SavingsAccountChargeAssembler savingsAccountChargeAssembler, AccountAssociationsRepository accountAssociationsRepository, FromJsonHelper fromJsonHelper, CalendarInstanceRepository calendarInstanceRepository, ConfigurationDomainService configurationDomainService, AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, - BusinessEventNotifierService businessEventNotifierService) { + BusinessEventNotifierService businessEventNotifierService, EntityManager entityManager) { return new DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl(context, savingAccountRepository, fixedDepositAccountRepository, recurringDepositAccountRepository, depositAccountAssembler, depositAccountDataValidator, accountNumberGenerator, clientRepository, groupRepository, savingsProductRepository, noteRepository, staffRepository, savingsAccountApplicationTransitionApiJsonValidator, savingsAccountChargeAssembler, accountAssociationsRepository, fromJsonHelper, calendarInstanceRepository, configurationDomainService, accountNumberFormatRepository, - businessEventNotifierService); + businessEventNotifierService, entityManager); } @Bean @@ -364,7 +365,7 @@ public SavingsAccountWritePlatformService savingsAccountWritePlatformService(Pla EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, AppUserRepositoryWrapper appuserRepository, StandingInstructionRepository standingInstructionRepository, BusinessEventNotifierService businessEventNotifierService, GSIMRepositoy gsimRepository, SavingsAccountInterestPostingService savingsAccountInterestPostingService, - ErrorHandler errorHandler) { + ErrorHandler errorHandler, EntityManager entityManager) { return new SavingsAccountWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, savingAccountRepositoryWrapper, staffRepository, savingsAccountTransactionRepository, savingAccountAssembler, savingsAccountTransactionDataValidator, savingsAccountChargeDataValidator, paymentDetailWritePlatformService, journalEntryWritePlatformService, @@ -372,7 +373,7 @@ public SavingsAccountWritePlatformService savingsAccountWritePlatformService(Pla chargeRepository, savingsAccountChargeRepository, holidayRepository, workingDaysRepository, configurationDomainService, depositAccountOnHoldTransactionRepository, entityDatatableChecksWritePlatformService, appuserRepository, standingInstructionRepository, businessEventNotifierService, gsimRepository, savingsAccountInterestPostingService, - errorHandler); + errorHandler, entityManager); } @Bean diff --git a/fineract-savings/dependencies.gradle b/fineract-savings/dependencies.gradle index 467fc305f44..0fae00b43f9 100644 --- a/fineract-savings/dependencies.gradle +++ b/fineract-savings/dependencies.gradle @@ -60,6 +60,14 @@ dependencies { implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') { exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence' } + + implementation('io.github.openfeign.querydsl:querydsl-core:6.2.1', + 'io.github.openfeign.querydsl:querydsl-jpa:6.2.1') + annotationProcessor( + 'io.github.openfeign.querydsl:querydsl-apt:6.2.1:jakarta', + 'jakarta.persistence:jakarta.persistence-api:3.1.0', + ) + // testCompile dependencies are ONLY used in src/test, not src/main. // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! // diff --git a/integration-tests/dependencies.gradle b/integration-tests/dependencies.gradle index 8b343892646..d1bff495da3 100644 --- a/integration-tests/dependencies.gradle +++ b/integration-tests/dependencies.gradle @@ -55,4 +55,9 @@ dependencies { testAnnotationProcessor 'org.mapstruct:mapstruct-processor' testImplementation 'com.github.tomakehurst:wiremock-standalone:3.0.1' + + testImplementation 'io.github.openfeign.querydsl:querydsl-core:6.2.1' + testImplementation 'io.github.openfeign.querydsl:querydsl-jpa:6.2.1' + testAnnotationProcessor 'io.github.openfeign.querydsl:querydsl-apt:6.2.1:jakarta' + testAnnotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0' }