diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/data/PendingMakerCheckerData.java b/fineract-core/src/main/java/org/apache/fineract/commands/data/PendingMakerCheckerData.java new file mode 100644 index 00000000000..588c2b70282 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/commands/data/PendingMakerCheckerData.java @@ -0,0 +1,49 @@ +/** + * 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.commands.data; + +import java.time.OffsetDateTime; +import lombok.Builder; +import lombok.Getter; + +/** + * Lightweight DTO representing a single pending maker-checker entry for a resource. + */ +@Getter +@Builder +public class PendingMakerCheckerData { + + /** The maker-checker command source id (m_portfolio_command_source.id) */ + private final Long id; + + /** e.g. "APPROVE", "DISBURSE", "CREATE", "ACTIVATE" */ + private final String actionName; + + /** e.g. "LOAN", "CLIENT", "SAVINGSACCOUNT" */ + private final String entityName; + + /** Human-readable label, e.g. "APPROVE_LOAN" */ + private final String permissionCode; + + /** Username of the maker who submitted this command */ + private final String makerUsername; + + /** When the maker submitted this command */ + private final OffsetDateTime madeOnDate; +} diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSourceRepository.java b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSourceRepository.java index ca0de426e7b..cdf0171de56 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSourceRepository.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/domain/CommandSourceRepository.java @@ -19,6 +19,7 @@ package org.apache.fineract.commands.domain; import java.time.OffsetDateTime; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; @@ -29,6 +30,15 @@ public interface CommandSourceRepository extends JpaRepository findPendingByLoanId(@Param("loanId") Long loanId, @Param("status") Integer status); + + @Query("select distinct c from CommandSource c join fetch c.maker where c.clientId = :clientId and c.status = :status order by c.madeOnDate desc") + List findPendingByClientId(@Param("clientId") Long clientId, @Param("status") Integer status); + + @Query("select distinct c from CommandSource c join fetch c.maker where c.savingsId = :savingsId and c.status = :status order by c.madeOnDate desc") + List findPendingBySavingsId(@Param("savingsId") Long savingsId, @Param("status") Integer status); + @Modifying(flushAutomatically = true) @Query("delete from CommandSource c where c.status = :status and c.madeOnDate is not null and c.madeOnDate <= :dateForPurgeCriteria") void deleteOlderEventsWithStatus(@Param("status") Integer status, @Param("dateForPurgeCriteria") OffsetDateTime dateForPurgeCriteria); diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/MakerCheckerReadService.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/MakerCheckerReadService.java new file mode 100644 index 00000000000..3829c75543c --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/MakerCheckerReadService.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.commands.service; + +import java.util.List; +import org.apache.fineract.commands.data.PendingMakerCheckerData; + +public interface MakerCheckerReadService { + + List retrievePendingByLoanId(Long loanId); + + List retrievePendingByClientId(Long clientId); + + List retrievePendingBySavingsId(Long savingsId); +} diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/MakerCheckerReadServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/MakerCheckerReadServiceImpl.java new file mode 100644 index 00000000000..c1db985d94c --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/MakerCheckerReadServiceImpl.java @@ -0,0 +1,60 @@ +/** + * 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.commands.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.commands.data.PendingMakerCheckerData; +import org.apache.fineract.commands.domain.CommandProcessingResultType; +import org.apache.fineract.commands.domain.CommandSource; +import org.apache.fineract.commands.domain.CommandSourceRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MakerCheckerReadServiceImpl implements MakerCheckerReadService { + + private final CommandSourceRepository commandSourceRepository; + + @Override + public List retrievePendingByLoanId(final Long loanId) { + return commandSourceRepository.findPendingByLoanId(loanId, CommandProcessingResultType.AWAITING_APPROVAL.getValue()).stream() + .map(this::toData).toList(); + } + + @Override + public List retrievePendingByClientId(final Long clientId) { + return commandSourceRepository.findPendingByClientId(clientId, CommandProcessingResultType.AWAITING_APPROVAL.getValue()).stream() + .map(this::toData).toList(); + } + + @Override + public List retrievePendingBySavingsId(final Long savingsId) { + return commandSourceRepository.findPendingBySavingsId(savingsId, CommandProcessingResultType.AWAITING_APPROVAL.getValue()).stream() + .map(this::toData).toList(); + } + + private PendingMakerCheckerData toData(final CommandSource cs) { + final String makerUsername = cs.getMaker() != null ? cs.getMaker().getUsername() : null; + return PendingMakerCheckerData.builder().id(cs.getId()).actionName(cs.getActionName()).entityName(cs.getEntityName()) + .permissionCode(cs.getPermissionCode()).makerUsername(makerUsername).madeOnDate(cs.getMadeOnDate()).build(); + } +} diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java index e0f0dc3be46..696a3d97c84 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java @@ -30,6 +30,7 @@ import org.apache.commons.lang3.builder.CompareToBuilder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.fineract.commands.data.PendingMakerCheckerData; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.ExternalId; @@ -115,6 +116,8 @@ public final class ClientData implements Comparable, Serializable { private List datatables; + private List pendingMakerCheckerApprovals; + // import fields private transient Integer rowIndex; private String dateFormat; diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java index 6733c2325f8..908c0d32bd7 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java @@ -32,6 +32,7 @@ import lombok.Setter; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.fineract.commands.data.PendingMakerCheckerData; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JsonLocalDateArrayFormat; import org.apache.fineract.infrastructure.dataqueries.data.DatatableData; @@ -95,6 +96,8 @@ public final class SavingsAccountData implements Serializable { private final Integer daysToDormancy; private final Integer daysToEscheat; private final BigDecimal savingsAmountOnHold; + + private List pendingMakerCheckerApprovals; // associations private final SavingsAccountSummaryData summary; @SuppressWarnings("unused") diff --git a/fineract-core/src/test/java/org/apache/fineract/commands/service/MakerCheckerReadServiceImplTest.java b/fineract-core/src/test/java/org/apache/fineract/commands/service/MakerCheckerReadServiceImplTest.java new file mode 100644 index 00000000000..f80a157c3ef --- /dev/null +++ b/fineract-core/src/test/java/org/apache/fineract/commands/service/MakerCheckerReadServiceImplTest.java @@ -0,0 +1,180 @@ +/** + * 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.commands.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.List; +import org.apache.fineract.commands.data.PendingMakerCheckerData; +import org.apache.fineract.commands.domain.CommandProcessingResultType; +import org.apache.fineract.commands.domain.CommandSource; +import org.apache.fineract.commands.domain.CommandSourceRepository; +import org.apache.fineract.useradministration.domain.AppUser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MakerCheckerReadServiceImplTest { + + @Mock + private CommandSourceRepository commandSourceRepository; + + @InjectMocks + private MakerCheckerReadServiceImpl service; + + private static final Integer AWAITING_STATUS = CommandProcessingResultType.AWAITING_APPROVAL.getValue(); + private static final OffsetDateTime NOW = OffsetDateTime.now(ZoneOffset.UTC); + + private CommandSource buildCommandSource(String action, String entity, String username) { + final AppUser maker = mock(AppUser.class); + when(maker.getUsername()).thenReturn(username); + + return CommandSource.builder().actionName(action).entityName(entity).maker(maker).madeOnDate(NOW) + .status(CommandProcessingResultType.AWAITING_APPROVAL.getValue()).sanitized(false).build(); + } + + @Test + void retrievePendingByLoanId_withPendingCommand_returnsMappedData() { + final Long loanId = 101L; + final CommandSource cs = buildCommandSource("APPROVE", "LOAN", "maker01"); + when(commandSourceRepository.findPendingByLoanId(loanId, AWAITING_STATUS)).thenReturn(List.of(cs)); + + final List result = service.retrievePendingByLoanId(loanId); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getActionName()).isEqualTo("APPROVE"); + assertThat(result.get(0).getEntityName()).isEqualTo("LOAN"); + assertThat(result.get(0).getPermissionCode()).isEqualTo("APPROVE_LOAN"); // computed: action + "_" + entity + assertThat(result.get(0).getMakerUsername()).isEqualTo("maker01"); + assertThat(result.get(0).getMadeOnDate()).isEqualTo(NOW); + verify(commandSourceRepository).findPendingByLoanId(loanId, AWAITING_STATUS); + } + + @Test + void retrievePendingByLoanId_withNoPendingCommands_returnsEmptyList() { + final Long loanId = 102L; + when(commandSourceRepository.findPendingByLoanId(loanId, AWAITING_STATUS)).thenReturn(Collections.emptyList()); + + final List result = service.retrievePendingByLoanId(loanId); + + assertThat(result).isEmpty(); + } + + @Test + void retrievePendingByLoanId_withMultiplePendingCommands_returnsAllMapped() { + final Long loanId = 103L; + final CommandSource cs1 = buildCommandSource("APPROVE", "LOAN", "maker01"); + final CommandSource cs2 = buildCommandSource("DISBURSE", "LOAN", "maker02"); + when(commandSourceRepository.findPendingByLoanId(loanId, AWAITING_STATUS)).thenReturn(List.of(cs1, cs2)); + + final List result = service.retrievePendingByLoanId(loanId); + + assertThat(result).hasSize(2); + assertThat(result.get(0).getActionName()).isEqualTo("APPROVE"); + assertThat(result.get(1).getActionName()).isEqualTo("DISBURSE"); + } + + @Test + void retrievePendingByLoanId_withNullMaker_returnsMakerUsernameNull() { + final Long loanId = 104L; + final CommandSource cs = CommandSource.builder().actionName("APPROVE").entityName("LOAN").maker(null).madeOnDate(NOW) + .status(CommandProcessingResultType.AWAITING_APPROVAL.getValue()).sanitized(false).build(); + when(commandSourceRepository.findPendingByLoanId(loanId, AWAITING_STATUS)).thenReturn(List.of(cs)); + + final List result = service.retrievePendingByLoanId(loanId); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getMakerUsername()).isNull(); + } + + @Test + void retrievePendingByClientId_withPendingCommand_returnsMappedData() { + final Long clientId = 201L; + final CommandSource cs = buildCommandSource("ACTIVATE", "CLIENT", "maker03"); + when(commandSourceRepository.findPendingByClientId(clientId, AWAITING_STATUS)).thenReturn(List.of(cs)); + + final List result = service.retrievePendingByClientId(clientId); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getActionName()).isEqualTo("ACTIVATE"); + assertThat(result.get(0).getEntityName()).isEqualTo("CLIENT"); + assertThat(result.get(0).getPermissionCode()).isEqualTo("ACTIVATE_CLIENT"); + assertThat(result.get(0).getMakerUsername()).isEqualTo("maker03"); + verify(commandSourceRepository).findPendingByClientId(clientId, AWAITING_STATUS); + } + + @Test + void retrievePendingByClientId_withNoPendingCommands_returnsEmptyList() { + final Long clientId = 202L; + when(commandSourceRepository.findPendingByClientId(clientId, AWAITING_STATUS)).thenReturn(Collections.emptyList()); + + final List result = service.retrievePendingByClientId(clientId); + + assertThat(result).isEmpty(); + } + + @Test + void retrievePendingBySavingsId_withPendingCommand_returnsMappedData() { + final Long savingsId = 301L; + final CommandSource cs = buildCommandSource("APPROVE", "SAVINGSACCOUNT", "maker04"); + when(commandSourceRepository.findPendingBySavingsId(savingsId, AWAITING_STATUS)).thenReturn(List.of(cs)); + + final List result = service.retrievePendingBySavingsId(savingsId); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getActionName()).isEqualTo("APPROVE"); + assertThat(result.get(0).getEntityName()).isEqualTo("SAVINGSACCOUNT"); + assertThat(result.get(0).getPermissionCode()).isEqualTo("APPROVE_SAVINGSACCOUNT"); + assertThat(result.get(0).getMakerUsername()).isEqualTo("maker04"); + verify(commandSourceRepository).findPendingBySavingsId(savingsId, AWAITING_STATUS); + } + + @Test + void retrievePendingBySavingsId_withNoPendingCommands_returnsEmptyList() { + final Long savingsId = 302L; + when(commandSourceRepository.findPendingBySavingsId(savingsId, AWAITING_STATUS)).thenReturn(Collections.emptyList()); + + final List result = service.retrievePendingBySavingsId(savingsId); + + assertThat(result).isEmpty(); + } + + @Test + void retrievePendingBySavingsId_coversFixedDeposit_returnsMappedData() { + final Long fdId = 401L; + final CommandSource cs = buildCommandSource("ACTIVATE", "FIXEDDEPOSITACCOUNT", "maker05"); + when(commandSourceRepository.findPendingBySavingsId(fdId, AWAITING_STATUS)).thenReturn(List.of(cs)); + + final List result = service.retrievePendingBySavingsId(fdId); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getEntityName()).isEqualTo("FIXEDDEPOSITACCOUNT"); + assertThat(result.get(0).getPermissionCode()).isEqualTo("ACTIVATE_FIXEDDEPOSITACCOUNT"); + assertThat(result.get(0).getMakerUsername()).isEqualTo("maker05"); + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java index 25a2e916d55..aa294adb444 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java @@ -31,6 +31,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +import org.apache.fineract.commands.data.PendingMakerCheckerData; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.StringEnumOptionData; @@ -295,6 +296,8 @@ public class LoanAccountData { private StringEnumOptionData buyDownFeeIncomeType; private Boolean merchantBuyDownFee; + private List pendingMakerCheckerApprovals; + public static LoanAccountData importInstanceIndividual(EnumOptionData loanTypeEnumOption, Long clientId, Long productId, Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments, Integer repaymentEvery, EnumOptionData repaidEveryFrequencyEnums, Integer loanTermFrequency, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/mapper/ClientMapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/mapper/ClientMapper.java index f8849cfaf32..cd8ac561be7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/mapper/ClientMapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/mapper/ClientMapper.java @@ -80,6 +80,7 @@ public interface ClientMapper { @Mapping(target = "legalFormId", ignore = true) @Mapping(target = "clientCollateralManagements", ignore = true) @Mapping(target = "groups", ignore = true) + @Mapping(target = "pendingMakerCheckerApprovals", ignore = true) ClientData map(Client source); @Named("clientTypeCode") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java index c790c6f5e5a..9251028388e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java @@ -30,6 +30,8 @@ import java.util.Set; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.commands.data.PendingMakerCheckerData; +import org.apache.fineract.commands.service.MakerCheckerReadService; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.core.data.ApiParameterError; @@ -87,6 +89,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService private final ClientRepositoryWrapper clientRepositoryWrapper; private final ClientMapper clientMapper; private final InputValidator inputValidator; + private final MakerCheckerReadService makerCheckerReadService; @Override public Page retrieveAll(final SearchParameters searchParameters) { @@ -243,7 +246,12 @@ public ClientData retrieveOne(final Long clientId) { final Collection parentGroups = this.jdbcTemplate.query(clientGroupsSql, this.clientGroupsMapper, // NOSONAR clientId); - return ClientData.setParentGroups(clientData, parentGroups, clientCollateralManagementDataSet); + final ClientData result = ClientData.setParentGroups(clientData, parentGroups, clientCollateralManagementDataSet); + + final List pending = makerCheckerReadService.retrievePendingByClientId(clientId); + result.setPendingMakerCheckerApprovals(pending.isEmpty() ? null : pending); + + return result; } catch (final EmptyResultDataAccessException e) { throw new ClientNotFoundException(clientId, e); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 5a4bda1f74f..dec4b53a5b8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -41,6 +41,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.commands.data.PendingMakerCheckerData; +import org.apache.fineract.commands.service.MakerCheckerReadService; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; @@ -200,6 +202,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa private final InterestRefundServiceDelegate interestRefundServiceDelegate; private final LoanMaximumAmountCalculator loanMaximumAmountCalculator; private final LoanRepaymentScheduleService loanRepaymentScheduleService; + private final MakerCheckerReadService makerCheckerReadService; @Override public LoanAccountData retrieveOne(final Long loanId) { @@ -217,7 +220,15 @@ public LoanAccountData retrieveOne(final Long loanId) { sqlBuilder.append(" left join m_office transferToOffice on transferToOffice.id = c.transfer_to_office_id "); sqlBuilder.append(" where l.id=? and ( o.hierarchy like ? or transferToOffice.hierarchy like ?)"); - return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), rm, loanId, hierarchySearchString, hierarchySearchString); + final LoanAccountData loanAccountData = this.jdbcTemplate.queryForObject(sqlBuilder.toString(), rm, loanId, + hierarchySearchString, hierarchySearchString); + + if (loanAccountData != null) { + final List pending = makerCheckerReadService.retrievePendingByLoanId(loanId); + loanAccountData.setPendingMakerCheckerApprovals(pending.isEmpty() ? null : pending); + } + + return loanAccountData; } catch (final EmptyResultDataAccessException e) { throw new LoanNotFoundException(loanId, e); } 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 292daf50bf7..d5c6cca0bff 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 @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanaccount.starter; import org.apache.fineract.cob.service.LoanAccountLockService; +import org.apache.fineract.commands.service.MakerCheckerReadService; import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; @@ -353,7 +354,7 @@ public LoanReadPlatformServiceImpl loanReadPlatformService(JdbcTemplate jdbcTemp LoanBalanceService loanBalanceService, LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository, LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository, @Lazy InterestRefundServiceDelegate interestRefundServiceDelegate, LoanMaximumAmountCalculator loanMaximumAmountCalculator, - LoanRepaymentScheduleService loanRepaymentScheduleService) { + LoanRepaymentScheduleService loanRepaymentScheduleService, MakerCheckerReadService makerCheckerReadService) { return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository, loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService, fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService, @@ -362,7 +363,7 @@ public LoanReadPlatformServiceImpl loanReadPlatformService(JdbcTemplate jdbcTemp delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService, loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService, loanCapitalizedIncomeBalanceRepository, loanBuyDownFeeBalanceRepository, interestRefundServiceDelegate, - loanMaximumAmountCalculator, loanRepaymentScheduleService); + loanMaximumAmountCalculator, loanRepaymentScheduleService, makerCheckerReadService); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java index 5f9aceb03b4..5ba7b20341b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java @@ -29,9 +29,12 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import lombok.RequiredArgsConstructor; +import org.apache.fineract.commands.data.PendingMakerCheckerData; +import org.apache.fineract.commands.service.MakerCheckerReadService; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.PaginationParameters; import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator; @@ -128,6 +131,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead private final DropdownReadPlatformService dropdownReadPlatformService; private final CalendarReadPlatformService calendarReadPlatformService; private final PaymentTypeReadService paymentTypeReadPlatformService; + private final MakerCheckerReadService makerCheckerReadService; @Override public Collection retrieveAll(final DepositAccountType depositAccountType, @@ -213,7 +217,15 @@ public DepositAccountData retrieveOne(final DepositAccountType depositAccountTyp sqlBuilder.append(depositAccountMapper.schema()); sqlBuilder.append(" where sa.id = ? and sa.deposit_type_enum = ? "); - return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositAccountMapper, accountId, depositAccountType.getValue()); + final DepositAccountData account = this.jdbcTemplate.queryForObject(sqlBuilder.toString(), depositAccountMapper, accountId, + depositAccountType.getValue()); + + if (account != null) { + final List pending = makerCheckerReadService.retrievePendingBySavingsId(accountId); + account.setPendingMakerCheckerApprovals(pending.isEmpty() ? null : pending); + } + + return account; } catch (final EmptyResultDataAccessException e) { throw new DepositAccountNotFoundException(depositAccountType, accountId, e); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 674e8442d1c..fd1160ad67e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -34,6 +34,8 @@ import org.apache.commons.lang3.StringUtils; import org.apache.fineract.accounting.common.AccountingRuleType; import org.apache.fineract.accounting.glaccount.data.GLAccountData; +import org.apache.fineract.commands.data.PendingMakerCheckerData; +import org.apache.fineract.commands.service.MakerCheckerReadService; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; @@ -108,16 +110,18 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead private final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper; private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; + private final MakerCheckerReadService makerCheckerReadService; public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate, final SavingsAccountAssembler savingAccountAssembler, PaginationHelper paginationHelper, ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, - SavingsAccountTransactionRepository savingsAccountTransactionRepository) { + SavingsAccountTransactionRepository savingsAccountTransactionRepository, MakerCheckerReadService makerCheckerReadService) { this.context = context; this.jdbcTemplate = jdbcTemplate; this.sqlGenerator = sqlGenerator; this.savingsAccountRepositoryWrapper = savingsAccountRepositoryWrapper; this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; + this.makerCheckerReadService = makerCheckerReadService; this.transactionTemplateMapper = new SavingsAccountTransactionTemplateMapper(); this.transactionsMapper = new SavingsAccountTransactionsMapper(); this.savingsAccountTransactionsForBatchMapper = new SavingsAccountTransactionsForBatchMapper(); @@ -221,7 +225,14 @@ public SavingsAccountData retrieveOne(final Long accountId) { try { final String sql = "select " + this.savingAccountMapper.schema() + " where sa.id = ?"; - return this.jdbcTemplate.queryForObject(sql, this.savingAccountMapper, new Object[] { accountId }); // NOSONAR + final SavingsAccountData data = this.jdbcTemplate.queryForObject(sql, this.savingAccountMapper, new Object[] { accountId }); // NOSONAR + + if (data != null) { + final List pending = makerCheckerReadService.retrievePendingBySavingsId(accountId); + data.setPendingMakerCheckerApprovals(pending.isEmpty() ? null : pending); + } + + return data; } catch (final EmptyResultDataAccessException e) { throw new SavingsAccountNotFoundException(accountId, e); } 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 f4960a5a9bb..ccc8448cff9 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 @@ -21,6 +21,7 @@ import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService; import org.apache.fineract.commands.service.CommandProcessingService; +import org.apache.fineract.commands.service.MakerCheckerReadService; import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; @@ -213,12 +214,13 @@ public DepositAccountReadPlatformService depositAccountReadPlatformService(Platf SavingsDropdownReadPlatformService savingsDropdownReadPlatformService, ChargeReadPlatformService chargeReadPlatformService, StaffReadService staffReadPlatformService, DepositsDropdownReadPlatformService depositsDropdownReadPlatformService, SavingsAccountReadPlatformService savingsAccountReadPlatformService, DropdownReadPlatformService dropdownReadPlatformService, - CalendarReadPlatformService calendarReadPlatformService, PaymentTypeReadService paymentTypeReadPlatformService) { + CalendarReadPlatformService calendarReadPlatformService, PaymentTypeReadService paymentTypeReadPlatformService, + MakerCheckerReadService makerCheckerReadService) { return new DepositAccountReadPlatformServiceImpl(context, jdbcTemplate, chartReadPlatformService, productChartReadPlatformService, paginationParametersDataValidator, sqlGenerator, paginationHelper, clientReadPlatformService, groupReadPlatformService, depositProductReadPlatformService, savingsDropdownReadPlatformService, chargeReadPlatformService, staffReadPlatformService, depositsDropdownReadPlatformService, savingsAccountReadPlatformService, dropdownReadPlatformService, - calendarReadPlatformService, paymentTypeReadPlatformService); + calendarReadPlatformService, paymentTypeReadPlatformService, makerCheckerReadService); } @Bean @@ -361,9 +363,9 @@ public SavingsAccountActivationService savingsAccountActivationService( public SavingsAccountReadPlatformService savingsAccountReadPlatformService(PlatformSecurityContext context, JdbcTemplate jdbcTemplate, SavingsAccountAssembler savingAccountAssembler, PaginationHelper paginationHelper, DatabaseSpecificSQLGenerator sqlGenerator, SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, ColumnValidator columnValidator, - SavingsAccountTransactionRepository savingsAccountTransactionRepository) { + SavingsAccountTransactionRepository savingsAccountTransactionRepository, MakerCheckerReadService makerCheckerReadService) { return new SavingsAccountReadPlatformServiceImpl(context, jdbcTemplate, savingAccountAssembler, paginationHelper, columnValidator, - sqlGenerator, savingsAccountRepositoryWrapper, savingsAccountTransactionRepository); + sqlGenerator, savingsAccountRepositoryWrapper, savingsAccountTransactionRepository, makerCheckerReadService); } @Bean diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImplTest.java index d05c78216c9..3f4634bfcec 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImplTest.java @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.Collections; +import org.apache.fineract.commands.service.MakerCheckerReadService; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.PaginationHelper; @@ -76,6 +77,8 @@ class ClientReadPlatformServiceImplTest { private ClientRepositoryWrapper clientRepositoryWrapper; @Mock private ClientMapper clientMapper; + @Mock + private MakerCheckerReadService makerCheckerReadService; @InjectMocks private ClientReadPlatformServiceImpl clientReadPlatformService; @@ -98,6 +101,7 @@ void testRetrieveOne_Success() { // Mock the groups query to return an empty list when(jdbcTemplate.query(anyString(), any(RowMapper.class), anyLong())).thenReturn(Collections.emptyList()); + when(makerCheckerReadService.retrievePendingByClientId(clientId)).thenReturn(Collections.emptyList()); // Act ClientData result = clientReadPlatformService.retrieveOne(clientId); diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountData.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountData.java index 540aabf2dd3..48f983df499 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountData.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountData.java @@ -21,9 +21,11 @@ import java.math.BigDecimal; import java.util.Collection; import java.util.HashSet; +import java.util.List; import lombok.Getter; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.fineract.commands.data.PendingMakerCheckerData; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.staff.data.StaffData; @@ -64,6 +66,8 @@ public class DepositAccountData { protected final boolean withHoldTax; protected final TaxGroupData taxGroup; + protected List pendingMakerCheckerApprovals; + // associations protected final SavingsAccountSummaryData summary; protected final Collection transactions; @@ -335,4 +339,12 @@ public int hashCode() { public Collection charges() { return (this.charges == null) ? new HashSet() : this.charges; } + + public List getPendingMakerCheckerApprovals() { + return pendingMakerCheckerApprovals; + } + + public void setPendingMakerCheckerApprovals(final List pendingMakerCheckerApprovals) { + this.pendingMakerCheckerApprovals = pendingMakerCheckerApprovals; + } }