FINERACT-2203: LoanTransactionsApi add getLoanTransactions

This commit is contained in:
adam.magyari
2025-03-19 17:38:19 +01:00
committed by Adam Saghy
parent a8bbba0b24
commit 2cfc0b2503
18 changed files with 763 additions and 19 deletions
@@ -20,11 +20,15 @@
package org.apache.fineract.infrastructure.core.jersey;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Singleton;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.ext.Provider;
import org.apache.fineract.infrastructure.core.api.jersey.PageableParamProvider;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.spi.internal.ValueParamProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
@@ -35,6 +39,13 @@ public class JerseyConfig extends ResourceConfig {
JerseyConfig() {
register(org.glassfish.jersey.media.multipart.MultiPartFeature.class);
register(new AbstractBinder() {
@Override
protected void configure() {
bind(PageableParamProvider.class).to(ValueParamProvider.class).in(Singleton.class);
}
});
property(ServerProperties.WADL_FEATURE_DISABLE, true);
}
@@ -45,12 +45,14 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.api.DateParam;
import org.apache.fineract.infrastructure.core.api.jersey.Pagination;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.DateFormat;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
@@ -64,12 +66,15 @@ import org.apache.fineract.infrastructure.security.service.PlatformSecurityConte
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
@Path("/v1/loans")
@@ -224,6 +229,40 @@ public class LoanTransactionsApiResource {
return retrieveTransaction(null, loanExternalId, null, externalTransactionId, uriInfo);
}
@GET
@Path("{loanId}/transactions")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve Transactions", description = "Retrieves transactions of a loan")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsResponse.class))) })
public Page<LoanTransactionData> retrieveTransactionsByLoanId(
@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
@QueryParam("excludedTypes") @Parameter(description = "excludedTypes", in = ParameterIn.QUERY, schema = @Schema(enumAsRef = true)) final List<LoanTransactionApiConstants.TransactionType> excludedTypes,
@QueryParam("page") @Parameter(description = "page") final Integer page,
@QueryParam("size") @Parameter(description = "size") final Integer size,
@QueryParam("sort") @Parameter(description = "sort") final String sort, @Parameter(hidden = true) @Pagination Pageable pageable,
@Context final UriInfo uriInfo) {
return retrieveTransactions(loanId, null, excludedTypes, pageable, uriInfo);
}
@GET
@Path("external-id/{loanExternalId}/transactions")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve Transactions", description = "Retrieves transactions of a loan")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsResponse.class))) })
public Page<LoanTransactionData> retrieveTransactionsByExternalLoanId(
@PathParam("loanExternalId") @Parameter(description = "loanId", required = true) final String loanExternalId,
@QueryParam("excludedTypes") @Parameter(description = "excludedTypes", in = ParameterIn.QUERY, schema = @Schema(enumAsRef = true)) final List<LoanTransactionApiConstants.TransactionType> excludedTypes,
@QueryParam("page") @Parameter(description = "page") final Integer page,
@QueryParam("size") @Parameter(description = "size") final Integer size,
@QueryParam("sort") @Parameter(description = "sort") final String sort, @Parameter(hidden = true) @Pagination Pageable pageable,
@Context final UriInfo uriInfo) {
return retrieveTransactions(null, loanExternalId, excludedTypes, pageable, uriInfo);
}
@POST
@Path("{loanId}/transactions")
@Consumes({ MediaType.APPLICATION_JSON })
@@ -439,6 +478,59 @@ public class LoanTransactionsApiResource {
return this.toApiJsonSerializer.serialize(settings, transactionData, this.responseDataParameters);
}
private Page<LoanTransactionData> retrieveTransactions(final Long loanId, final String loanExternalIdStr,
final List<LoanTransactionApiConstants.TransactionType> excludedTypes, Pageable pageable, final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
Long resolvedLoanId = getResolvedLoanIdWithExistsCheck(loanId, loanExternalId);
Set<LoanTransactionType> excludedTransactionTypes = new HashSet<>();
if (CollectionUtils.isNotEmpty(excludedTypes)) {
excludedTypes.forEach(x -> excludedTransactionTypes.add(transactionTypeFromParam(x)));
}
return loanReadPlatformService.retrieveLoanTransactions(resolvedLoanId, excludedTransactionTypes, pageable);
}
private LoanTransactionType transactionTypeFromParam(LoanTransactionApiConstants.TransactionType transactionTypeParam) {
return switch (transactionTypeParam) {
case disbursement -> LoanTransactionType.DISBURSEMENT;
case repayment -> LoanTransactionType.REPAYMENT;
case waiver -> LoanTransactionType.WAIVE_INTEREST;
case repaymentAtDisbursement -> LoanTransactionType.REPAYMENT_AT_DISBURSEMENT;
case writeOff -> LoanTransactionType.WRITEOFF;
case markedForRescheduling -> LoanTransactionType.MARKED_FOR_RESCHEDULING;
case recoveryRepayment -> LoanTransactionType.RECOVERY_REPAYMENT;
case waiveCharges -> LoanTransactionType.WAIVE_CHARGES;
case accrual -> LoanTransactionType.ACCRUAL;
case initiateTransfer -> LoanTransactionType.INITIATE_TRANSFER;
case approveTransfer -> LoanTransactionType.APPROVE_TRANSFER;
case withdrawTransfer -> LoanTransactionType.WITHDRAW_TRANSFER;
case rejectTransfer -> LoanTransactionType.REJECT_TRANSFER;
case refund -> LoanTransactionType.REFUND;
case chargePayment -> LoanTransactionType.CHARGE_PAYMENT;
case incomePosting -> LoanTransactionType.INCOME_POSTING;
case creditBalanceRefund -> LoanTransactionType.CREDIT_BALANCE_REFUND;
case merchantIssuedRefund -> LoanTransactionType.MERCHANT_ISSUED_REFUND;
case payoutRefund -> LoanTransactionType.PAYOUT_REFUND;
case goodwillCredit -> LoanTransactionType.GOODWILL_CREDIT;
case chargeRefund -> LoanTransactionType.CHARGE_REFUND;
case chargeback -> LoanTransactionType.CHARGEBACK;
case chargeAdjustment -> LoanTransactionType.CHARGE_ADJUSTMENT;
case chargeOff -> LoanTransactionType.CHARGE_OFF;
case reAge -> LoanTransactionType.REAGE;
case reAmortize -> LoanTransactionType.REAMORTIZE;
case interestPaymentWaiver -> LoanTransactionType.INTEREST_PAYMENT_WAIVER;
case accrualActivity -> LoanTransactionType.ACCRUAL_ACTIVITY;
case interestRefund -> LoanTransactionType.INTEREST_REFUND;
case accrualAdjustment -> LoanTransactionType.ACCRUAL_ADJUSTMENT;
default ->
throw new InvalidLoanTransactionTypeException("transaction", transactionTypeParam.name(), "Unknown transaction type");
};
}
private String executeTransaction(final Long loanId, final String loanExternalIdStr, final String commandParam,
final String apiRequestBodyAsJson) {
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
@@ -637,4 +729,21 @@ public class LoanTransactionsApiResource {
}
return resolvedLoanId;
}
private Long getResolvedLoanIdWithExistsCheck(final Long loanId, final ExternalId loanExternalId) {
if (loanId != null) {
if (!loanReadPlatformService.existsByLoanId(loanId)) {
throw new LoanNotFoundException(loanId);
}
return loanId;
} else if (loanExternalId != null) {
final Long resolvedLoanId = loanReadPlatformService.retrieveLoanIdByExternalId(loanExternalId);
if (resolvedLoanId == null) {
throw new LoanNotFoundException(loanExternalId);
}
return resolvedLoanId;
} else {
throw new IllegalArgumentException("loanId and loanExternalId cannot be both null");
}
}
}
@@ -18,9 +18,11 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
@@ -39,6 +41,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.springframework.data.domain.Pageable;
public interface LoanReadPlatformService {
@@ -51,6 +54,9 @@ public interface LoanReadPlatformService {
Collection<LoanTransactionData> retrieveLoanTransactions(Long loanId);
org.springframework.data.domain.Page<LoanTransactionData> retrieveLoanTransactions(@NotNull Long loanId,
Set<LoanTransactionType> excludedTransactionTypes, Pageable pageable);
LoanAccountData retrieveTemplateWithClientAndProductDetails(Long clientId, Long productId);
LoanAccountData retrieveTemplateWithGroupAndProductDetails(Long groupId, Long productId);
@@ -143,4 +149,6 @@ public interface LoanReadPlatformService {
Long retrieveLoanIdByExternalId(ExternalId externalId);
List<Long> retrieveLoanIdsByExternalIds(List<ExternalId> externalIds);
boolean existsByLoanId(Long loanId);
}
@@ -21,6 +21,8 @@ package org.apache.fineract.portfolio.loanaccount.service;
import static java.lang.Boolean.TRUE;
import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.interestType;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -120,6 +122,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionMapper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanForeclosureValidator;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
@@ -134,6 +137,7 @@ import org.apache.fineract.useradministration.domain.AppUser;
import org.jetbrains.annotations.NotNull;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.Pageable;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
@@ -174,6 +178,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
private final LoanChargePaidByReadService loanChargePaidByReadService;
private final LoanTransactionRelationReadService loanTransactionRelationReadService;
private final LoanForeclosureValidator loanForeclosureValidator;
private final LoanTransactionMapper loanTransactionMapper;
@Override
public LoanAccountData retrieveOne(final Long loanId) {
@@ -598,6 +603,27 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
}
}
@Override
public org.springframework.data.domain.Page<LoanTransactionData> retrieveLoanTransactions(
@jakarta.validation.constraints.NotNull Long loanId, Set<LoanTransactionType> excludedTransactionTypes, Pageable pageable) {
final org.springframework.data.domain.Page<LoanTransaction> transactionPage = loanTransactionRepository
.findAll((root, query, builder) -> {
List<Predicate> predicates = new ArrayList<>();
final Join<Object, Object> loanjoin = root.join("loan");
predicates.add(builder.equal(loanjoin.get("id"), loanId));
if (excludedTransactionTypes != null && !excludedTransactionTypes.isEmpty()) {
final List<Integer> excludedTransactionTypeValues = excludedTransactionTypes.stream()
.map(LoanTransactionType::getValue).toList();
predicates.add(builder.not(root.get("typeOf").in(excludedTransactionTypeValues)));
}
return builder.and(predicates.toArray(new Predicate[] {}));
}, pageable);
return transactionPage.map(loanTransactionMapper::mapLoanTransaction);
}
@Override
public Long getLoanIdByLoanExternalId(String externalId) {
ExternalId loanExternalId = ExternalIdFactory.produce(externalId);
@@ -2182,4 +2208,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
return loanRepositoryWrapper.findIdByExternalIds(externalIds);
}
@Override
public boolean existsByLoanId(Long loanId) {
return loanRepositoryWrapper.existsByLoanId(loanId);
}
}
@@ -85,6 +85,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanSchedu
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanChargeMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanCollateralManagementMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionMapper;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
@@ -325,7 +326,7 @@ public class LoanAccountConfiguration {
ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator,
DelinquencyReadPlatformService delinquencyReadPlatformService, LoanTransactionRepository loanTransactionRepository,
LoanChargePaidByReadService loanChargePaidByReadService, LoanTransactionRelationReadService loanTransactionRelationReadService,
LoanForeclosureValidator loanForeclosureValidator) {
LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper) {
return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository,
loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService,
fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService,
@@ -333,7 +334,7 @@ public class LoanAccountConfiguration {
loanRepaymentScheduleTransactionProcessorFactory, floatingRatesReadPlatformService, loanUtilService,
configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator,
delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService,
loanForeclosureValidator);
loanForeclosureValidator, loanTransactionMapper);
}
@Bean