FINERACT-2348: Calculate and store Capitalized income / Buydown fee amortization allocation schedule

This commit is contained in:
mariiaKraievska
2025-09-08 14:00:06 +02:00
committed by Adam Saghy
parent 9790f59e4a
commit cd0ecfcda0
34 changed files with 2094 additions and 91 deletions
@@ -31,14 +31,24 @@ import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.data.BuyDownFeeAmortizationDetails;
import org.apache.fineract.portfolio.loanaccount.data.LoanAmortizationAllocationData;
import org.apache.fineract.portfolio.loanaccount.service.BuyDownFeeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAmortizationAllocationService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.springframework.stereotype.Component;
@@ -48,9 +58,15 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor
public class LoanBuyDownFeeApiResource {
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("loanId", "loanExternalId", "baseLoanTransactionId", "baseLoanTransactionDate", "baseLoanTransactionAmount",
"unrecognizedAmount", "chargedOffAmount", "adjustmentAmount", "amortizationMappings"));
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN";
private final PlatformSecurityContext context;
private final BuyDownFeeReadPlatformService buyDownFeeReadPlatformService;
private final LoanAmortizationAllocationService loanAmortizationAllocationService;
private final DefaultToApiJsonSerializer<LoanAmortizationAllocationData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final LoanReadPlatformService loanReadPlatformService;
@Path("/{loanId}/buydown-fees")
@@ -84,4 +100,96 @@ public class LoanBuyDownFeeApiResource {
return this.buyDownFeeReadPlatformService.retrieveLoanBuyDownFeeAmortizationDetails(resolvedLoanId);
}
/**
* Get BuyDown Fees allocation data by loan ID and transaction ID
*/
@GET
@Path("{loanId}/buydown-fees/{loanTransactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a BuyDown Fees allocation data", description = "Retrieves BuyDown Fees allocation data according to the Loan ID and Loan Transaction ID"
+ "Example Requests:\n" + "\n" + "/loans/1/buydown-fees/1\n" + "\n" + "\n"
+ "/loans/1/buydown-fees/1?fields=baseLoanTransaction,unrecognizedAmount")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanAmortizationAllocationApiResourceSwagger.LoanAmortizationAllocationResponse.class))) })
public String retrieveBuyDownFeesAllocationData(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("loanTransactionId") @Parameter(description = "loanTransactionId") final Long loanTransactionId,
@Context final UriInfo uriInfo) {
return retrieveBuyDownFeesAllocationData(loanId, null, loanTransactionId, null, uriInfo);
}
/**
* Get BuyDown Fees allocation data by loan external ID and transaction ID
*/
@GET
@Path("external-id/{loanExternalId}/buydown-fees/{loanTransactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a BuyDown Fees allocation data", description = "Retrieves BuyDown Fees allocation data according to the Loan external ID and Loan Transaction ID"
+ "Example Requests:\n" + "\n" + "/loans/external-id/1/buydown-fees/1\n" + "\n" + "\n"
+ "/loans/external-id/1/buydown-fees/1?fields=baseLoanTransaction,unrecognizedAmount")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanAmortizationAllocationApiResourceSwagger.LoanAmortizationAllocationResponse.class))) })
public String getBuyDownFeesAllocationDataByLoanExternalId(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanTransactionId") @Parameter(description = "loanTransactionId") final Long loanTransactionId,
@Context final UriInfo uriInfo) {
return retrieveBuyDownFeesAllocationData(null, loanExternalId, loanTransactionId, null, uriInfo);
}
/**
* Get BuyDown Fees allocation data by loan ID and transaction external ID
*/
@GET
@Path("{loanId}/buydown-fees/external-id/{loanTransactionExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a BuyDown Fees allocation data", description = "Retrieves BuyDown Fees allocation data according to the Loan ID and Loan Transaction external ID"
+ "Example Requests:\n" + "\n" + "/loans/1/buydown-fees/external-id/1\n" + "\n" + "\n"
+ "/loans/1/buydown-fees/external-id/1?fields=baseLoanTransaction,unrecognizedAmount")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanAmortizationAllocationApiResourceSwagger.LoanAmortizationAllocationResponse.class))) })
public String getBuyDownFeesAllocationDataByTransactionExternalId(
@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("loanTransactionExternalId") @Parameter(description = "loanTransactionExternalId") final String loanTransactionExternalId,
@Context final UriInfo uriInfo) {
return retrieveBuyDownFeesAllocationData(loanId, null, null, loanTransactionExternalId, uriInfo);
}
/**
* Get BuyDown Fees allocation data by loan external ID and transaction external ID
*/
@GET
@Path("external-id/{loanExternalId}/buydown-fees/external-id/{loanTransactionExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a BuyDown Fees allocation data", description = "Retrieves BuyDown Fees allocation data according to the Loan external ID and Loan Transaction external ID"
+ "Example Requests:\n" + "\n" + "/loans/external-id/1/buydown-fees/1\n" + "\n" + "\n"
+ "/loans/external-id/1/buydown-fees/1?fields=baseLoanTransaction,unrecognizedAmount")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanAmortizationAllocationApiResourceSwagger.LoanAmortizationAllocationResponse.class))) })
public String getBuyDownFeesAllocationDataByExternalIds(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanTransactionExternalId") @Parameter(description = "loanTransactionExternalId") final String loanTransactionExternalId,
@Context final UriInfo uriInfo) {
return retrieveBuyDownFeesAllocationData(null, loanExternalId, null, loanTransactionExternalId, uriInfo);
}
private String retrieveBuyDownFeesAllocationData(final Long loanId, final String loanExternalIdStr, final Long loanTransactionId,
final String loanTransactionExternalIdStr, final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
final ExternalId loanTransactionExternalId = ExternalIdFactory.produce(loanTransactionExternalIdStr);
final Long resolvedLoanId = loanId == null ? loanReadPlatformService.getResolvedLoanId(loanExternalId) : loanId;
final Long resolvedLoanTransactionId = loanReadPlatformService.getResolvedLoanTransactionId(loanTransactionId,
loanTransactionExternalId);
final LoanAmortizationAllocationData loanAmortizationAllocationData = loanAmortizationAllocationService
.retrieveLoanAmortizationAllocationsForBuyDownFeeTransaction(resolvedLoanTransactionId, resolvedLoanId);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, loanAmortizationAllocationData, RESPONSE_DATA_PARAMETERS);
}
}
@@ -30,15 +30,25 @@ import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.data.CapitalizedIncomeDetails;
import org.apache.fineract.portfolio.loanaccount.data.LoanAmortizationAllocationData;
import org.apache.fineract.portfolio.loanaccount.data.LoanCapitalizedIncomeData;
import org.apache.fineract.portfolio.loanaccount.service.CapitalizedIncomeBalanceReadService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAmortizationAllocationService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.springframework.stereotype.Component;
@@ -48,9 +58,15 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor
public class LoanCapitalizedIncomeApiResource {
private static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("loanId", "loanExternalId", "baseLoanTransactionId", "baseLoanTransactionDate", "baseLoanTransactionAmount",
"unrecognizedAmount", "chargedOffAmount", "adjustmentAmount", "amortizationMappings"));
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN";
private final PlatformSecurityContext context;
private final CapitalizedIncomeBalanceReadService capitalizedIncomeBalanceReadService;
private final LoanAmortizationAllocationService loanAmortizationAllocationService;
private final DefaultToApiJsonSerializer<LoanAmortizationAllocationData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final LoanReadPlatformService loanReadPlatformService;
@Path("/{loanId}/deferredincome")
@@ -109,4 +125,96 @@ public class LoanCapitalizedIncomeApiResource {
return this.capitalizedIncomeBalanceReadService.fetchLoanCapitalizedIncomeDetails(resolvedLoanId);
}
/**
* Get capitalized income allocation data by loan ID and transaction ID
*/
@GET
@Path("{loanId}/capitalized-incomes/{loanTransactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a capitalized income allocation data", description = "Retrieves capitalized income allocation data according to the Loan ID and Loan Transaction ID"
+ "Example Requests:\n" + "\n" + "/loans/1/capitalized-incomes/1\n" + "\n" + "\n"
+ "/loans/1/capitalized-incomes/1?fields=baseLoanTransaction,unrecognizedAmount")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanAmortizationAllocationApiResourceSwagger.LoanAmortizationAllocationResponse.class))) })
public String retrieveCapitalizedIncomeAllocationData(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("loanTransactionId") @Parameter(description = "loanTransactionId") final Long loanTransactionId,
@Context final UriInfo uriInfo) {
return retrieveCapitalizedIncomeAllocationData(loanId, null, loanTransactionId, null, uriInfo);
}
/**
* Get capitalized income allocation data by loan external ID and transaction ID
*/
@GET
@Path("external-id/{loanExternalId}/capitalized-incomes/{loanTransactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a capitalized income allocation data", description = "Retrieves capitalized income allocation data according to the Loan external ID and Loan Transaction ID"
+ "Example Requests:\n" + "\n" + "/loans/external-id/1/capitalized-incomes/1\n" + "\n" + "\n"
+ "/loans/external-id/1/capitalized-incomes/1?fields=baseLoanTransaction,unrecognizedAmount")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanAmortizationAllocationApiResourceSwagger.LoanAmortizationAllocationResponse.class))) })
public String getCapitalizedIncomeAllocationDataByLoanExternalId(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanTransactionId") @Parameter(description = "loanTransactionId") final Long loanTransactionId,
@Context final UriInfo uriInfo) {
return retrieveCapitalizedIncomeAllocationData(null, loanExternalId, loanTransactionId, null, uriInfo);
}
/**
* Get capitalized income allocation data by loan ID and transaction external ID
*/
@GET
@Path("{loanId}/capitalized-incomes/external-id/{loanTransactionExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a capitalized income allocation data", description = "Retrieves capitalized income allocation data according to the Loan ID and Loan Transaction external ID"
+ "Example Requests:\n" + "\n" + "/loans/1/capitalized-incomes/external-id/1\n" + "\n" + "\n"
+ "/loans/1/capitalized-incomes/external-id/1?fields=baseLoanTransaction,unrecognizedAmount")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanAmortizationAllocationApiResourceSwagger.LoanAmortizationAllocationResponse.class))) })
public String getCapitalizedIncomeAllocationDataByTransactionExternalId(
@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("loanTransactionExternalId") @Parameter(description = "loanTransactionExternalId") final String loanTransactionExternalId,
@Context final UriInfo uriInfo) {
return retrieveCapitalizedIncomeAllocationData(loanId, null, null, loanTransactionExternalId, uriInfo);
}
/**
* Get capitalized income allocation data by loan external ID and transaction external ID
*/
@GET
@Path("external-id/{loanExternalId}/capitalized-incomes/external-id/{loanTransactionExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a capitalized income allocation data", description = "Retrieves capitalized income allocation data according to the Loan external ID and Loan Transaction external ID"
+ "Example Requests:\n" + "\n" + "/loans/external-id/1/capitalized-incomes/1\n" + "\n" + "\n"
+ "/loans/external-id/1/capitalized-incomes/1?fields=baseLoanTransaction,unrecognizedAmount")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanAmortizationAllocationApiResourceSwagger.LoanAmortizationAllocationResponse.class))) })
public String getCapitalizedIncomeAllocationDataByExternalIds(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId,
@PathParam("loanTransactionExternalId") @Parameter(description = "loanTransactionExternalId") final String loanTransactionExternalId,
@Context final UriInfo uriInfo) {
return retrieveCapitalizedIncomeAllocationData(null, loanExternalId, null, loanTransactionExternalId, uriInfo);
}
private String retrieveCapitalizedIncomeAllocationData(final Long loanId, final String loanExternalIdStr, final Long loanTransactionId,
final String loanTransactionExternalIdStr, final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
final ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
final ExternalId loanTransactionExternalId = ExternalIdFactory.produce(loanTransactionExternalIdStr);
final Long resolvedLoanId = loanId == null ? loanReadPlatformService.getResolvedLoanId(loanExternalId) : loanId;
final Long resolvedLoanTransactionId = loanReadPlatformService.getResolvedLoanTransactionId(loanTransactionId,
loanTransactionExternalId);
final LoanAmortizationAllocationData loanAmortizationAllocationData = loanAmortizationAllocationService
.retrieveLoanAmortizationAllocationsForCapitalizedIncomeTransaction(resolvedLoanTransactionId, resolvedLoanId);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, loanAmortizationAllocationData, RESPONSE_DATA_PARAMETERS);
}
}
@@ -62,4 +62,9 @@ public class LoanBuyDownFeeBalance extends AbstractAuditableWithUTCDateTimeCusto
@Column(name = "amount_adjustment", scale = 6, precision = 19)
private BigDecimal amountAdjustment;
@Column(name = "is_deleted", nullable = false)
private boolean deleted = false;
@Column(name = "is_closed", nullable = false)
private boolean closed = false;
}
@@ -61,4 +61,10 @@ public class LoanCapitalizedIncomeBalance extends AbstractAuditableWithUTCDateTi
@Column(name = "amount_adjustment", scale = 6, precision = 19)
private BigDecimal amountAdjustment;
@Column(name = "is_deleted", nullable = false)
private boolean deleted = false;
@Column(name = "is_closed", nullable = false)
private boolean closed = false;
}
@@ -19,18 +19,34 @@
package org.apache.fineract.portfolio.loanaccount.repository;
import java.util.List;
import org.apache.fineract.portfolio.loanaccount.data.AmortizationAllocationBaseTransactionDTO;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
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 LoanBuyDownFeeBalanceRepository
extends JpaRepository<LoanBuyDownFeeBalance, Long>, JpaSpecificationExecutor<LoanBuyDownFeeBalance> {
List<LoanBuyDownFeeBalance> findAllByLoanId(Long loanId);
@Query("SELECT lbdfb FROM LoanBuyDownFeeBalance lbdfb WHERE lbdfb.loan.id=:loanId AND lbdfb.closed = FALSE ORDER BY lbdfb.date, lbdfb.createdDate")
List<LoanBuyDownFeeBalance> findAllByLoanIdAndClosedFalse(@Param("loanId") Long loanId);
LoanBuyDownFeeBalance findByLoanIdAndLoanTransactionId(Long loanId, Long transactionId);
@Query("SELECT lbdfb FROM LoanBuyDownFeeBalance lbdfb WHERE lbdfb.loan.id=:loanId AND lbdfb.closed = FALSE AND lbdfb.deleted = FALSE ORDER BY lbdfb.date, lbdfb.createdDate")
List<LoanBuyDownFeeBalance> findAllByLoanIdAndDeletedFalseAndClosedFalse(@Param("loanId") Long loanId);
@Query("SELECT lbdfb FROM LoanBuyDownFeeBalance lbdfb, LoanTransaction lt, LoanTransactionRelation ltr WHERE lt.loan.id = lbdfb.loan.id AND ltr.fromTransaction.id =:transactionId AND ltr.toTransaction.id=lt.id AND lbdfb.loanTransaction.id = lt.id")
LoanBuyDownFeeBalance findBalanceForAdjustment(Long transactionId);
LoanBuyDownFeeBalance findByLoanIdAndLoanTransactionIdAndDeletedFalseAndClosedFalse(Long loanId, Long transactionId);
@Query("SELECT lbdfb FROM LoanBuyDownFeeBalance lbdfb, LoanTransaction lt, LoanTransactionRelation ltr WHERE lt.loan.id = lbdfb.loan.id AND ltr.fromTransaction.id =:transactionId AND ltr.toTransaction.id=lt.id AND lbdfb.loanTransaction.id = lt.id AND lbdfb.deleted = false AND lbdfb.closed = false")
LoanBuyDownFeeBalance findBalanceForAdjustment(@Param("transactionId") Long transactionId);
@Query("""
SELECT new org.apache.fineract.portfolio.loanaccount.data.AmortizationAllocationBaseTransactionDTO(
l.id, l.externalId, lbdfb.loanTransaction.id, lbdfb.date, lbdfb.amount,
lbdfb.unrecognizedAmount, lbdfb.chargedOffAmount, lbdfb.amountAdjustment
) FROM LoanBuyDownFeeBalance lbdfb JOIN lbdfb.loan l JOIN lbdfb.loanTransaction bt
WHERE lbdfb.loanTransaction.id = :loanTransactionId AND l.id = :loanId
""")
AmortizationAllocationBaseTransactionDTO findBaseTransactionInfo(@Param("loanTransactionId") Long loanTransactionId,
@Param("loanId") Long loanId);
}
@@ -20,30 +20,47 @@ package org.apache.fineract.portfolio.loanaccount.repository;
import java.math.BigDecimal;
import java.util.List;
import org.apache.fineract.portfolio.loanaccount.data.AmortizationAllocationBaseTransactionDTO;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData;
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 LoanCapitalizedIncomeBalanceRepository extends JpaRepository<LoanCapitalizedIncomeBalance, Long>,
JpaSpecificationExecutor<LoanCapitalizedIncomeBalance>, CustomizedLoanCapitalizedIncomeBalanceRepository {
String FIND_BALANCE_REPAYMENT_SCHEDULE_DATA = "SELECT new org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData(lcib.loanTransaction.id, lcib.loan.id, lcib.loanTransaction.dateOf, lcib.loanTransaction.reversed, lcib.amount, lcib.unrecognizedAmount, lcib.loanTransaction.feeChargesPortion) FROM LoanCapitalizedIncomeBalance lcib ";
List<LoanCapitalizedIncomeBalance> findAllByLoanId(Long loanId);
@Query("SELECT lcib FROM LoanCapitalizedIncomeBalance lcib WHERE lcib.loan.id=:loanId AND lcib.closed = FALSE ORDER BY lcib.date, lcib.createdDate")
List<LoanCapitalizedIncomeBalance> findAllByLoanIdAndClosedFalse(@Param("loanId") Long loanId);
LoanCapitalizedIncomeBalance findByLoanIdAndLoanTransactionId(Long loanId, Long transactionId);
@Query("SELECT lcib FROM LoanCapitalizedIncomeBalance lcib WHERE lcib.loan.id=:loanId AND lcib.closed = FALSE AND lcib.deleted = FALSE ORDER BY lcib.date, lcib.createdDate")
List<LoanCapitalizedIncomeBalance> findAllByLoanIdAndDeletedFalseAndClosedFalse(@Param("loanId") Long loanId);
@Query(FIND_BALANCE_REPAYMENT_SCHEDULE_DATA + " WHERE lcib.loan.id = :loanId")
LoanCapitalizedIncomeBalance findByLoanIdAndLoanTransactionIdAndDeletedFalseAndClosedFalse(Long loanId, Long transactionId);
@Query(FIND_BALANCE_REPAYMENT_SCHEDULE_DATA
+ " WHERE lcib.loan.id = :loanId AND lcib.deleted = false AND lcib.closed = false ORDER BY lcib.date, lcib.createdDate")
List<LoanTransactionRepaymentPeriodData> findRepaymentPeriodDataByLoanId(Long loanId);
@Query("SELECT SUM(lcib.amount) FROM LoanCapitalizedIncomeBalance lcib WHERE lcib.loan.id = :loanId")
@Query("SELECT SUM(lcib.amount) FROM LoanCapitalizedIncomeBalance lcib WHERE lcib.loan.id = :loanId AND lcib.deleted = false AND lcib.closed = false")
BigDecimal calculateCapitalizedIncome(Long loanId);
@Query("SELECT SUM(lcib.amountAdjustment) FROM LoanCapitalizedIncomeBalance lcib WHERE lcib.loan.id = :loanId")
@Query("SELECT SUM(lcib.amountAdjustment) FROM LoanCapitalizedIncomeBalance lcib WHERE lcib.loan.id = :loanId AND lcib.deleted = false AND lcib.closed = false")
BigDecimal calculateCapitalizedIncomeAdjustment(Long loanId);
@Query("SELECT lcib FROM LoanCapitalizedIncomeBalance lcib, LoanTransaction lt, LoanTransactionRelation ltr WHERE lt.loan.id = lcib.loan.id AND ltr.fromTransaction.id =:transactionId AND ltr.toTransaction.id=lt.id AND lcib.loanTransaction.id = lt.id")
@Query("SELECT lcib FROM LoanCapitalizedIncomeBalance lcib, LoanTransaction lt, LoanTransactionRelation ltr WHERE lt.loan.id = lcib.loan.id AND ltr.fromTransaction.id =:transactionId AND ltr.toTransaction.id=lt.id AND lcib.loanTransaction.id = lt.id AND lcib.deleted = false AND lcib.closed = false")
LoanCapitalizedIncomeBalance findBalanceForAdjustment(Long transactionId);
@Query("""
SELECT new org.apache.fineract.portfolio.loanaccount.data.AmortizationAllocationBaseTransactionDTO(
l.id, l.externalId, lcib.loanTransaction.id, lcib.date, lcib.amount,
lcib.unrecognizedAmount, lcib.chargedOffAmount, lcib.amountAdjustment
) FROM LoanCapitalizedIncomeBalance lcib JOIN lcib.loan l JOIN lcib.loanTransaction bt
WHERE lcib.loanTransaction.id = :loanTransactionId AND l.id = :loanId
""")
AmortizationAllocationBaseTransactionDTO findBaseTransactionInfo(@Param("loanTransactionId") Long loanTransactionId,
@Param("loanId") Long loanId);
}
@@ -45,7 +45,8 @@ public class BuyDownFeeReadPlatformServiceImpl implements BuyDownFeeReadPlatform
throw new GeneralPlatformDomainRuleException("error.msg.loan.is.not.enabled.buydown.fee",
"Loan: %s is not enabled Buydown fee feature".formatted(loanId), loanId);
}
final List<LoanBuyDownFeeBalance> buyDownFeeBalances = loanBuyDownFeeBalanceRepository.findAllByLoanId(loanId);
final List<LoanBuyDownFeeBalance> buyDownFeeBalances = loanBuyDownFeeBalanceRepository
.findAllByLoanIdAndDeletedFalseAndClosedFalse(loanId);
return buyDownFeeBalances.stream().map(this::mapToLoanBuyDownFeeAmortizationData).collect(Collectors.toList());
}
@@ -165,8 +165,8 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
LoanTransaction savedBuyDownFeeAdjustment = loanTransactionRepository.saveAndFlush(buyDownFeeAdjustment);
// Update buy down fee balance
LoanBuyDownFeeBalance buydownFeeBalance = loanBuyDownFeeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
buyDownFeeTransactionId);
LoanBuyDownFeeBalance buydownFeeBalance = loanBuyDownFeeBalanceRepository
.findByLoanIdAndLoanTransactionIdAndDeletedFalseAndClosedFalse(loanId, buyDownFeeTransactionId);
if (buydownFeeBalance != null) {
buydownFeeBalance.setAmountAdjustment(MathUtil.nullToZero(buydownFeeBalance.getAmountAdjustment()).add(transactionAmount));
buydownFeeBalance
@@ -43,7 +43,8 @@ public class CapitalizedIncomeBalanceReadServiceImpl implements CapitalizedIncom
if (loanRepository.isEnabledCapitalizedIncome(loanId)) {
List<CapitalizedIncomeDetails> capitalizedIncomeData = new ArrayList<>();
List<LoanCapitalizedIncomeBalance> capitalizedIncomeBalances = capitalizedIncomeBalanceRepository.findAllByLoanId(loanId);
List<LoanCapitalizedIncomeBalance> capitalizedIncomeBalances = capitalizedIncomeBalanceRepository
.findAllByLoanIdAndDeletedFalseAndClosedFalse(loanId);
for (final LoanCapitalizedIncomeBalance capitalizedIncomeBalance : capitalizedIncomeBalances) {
final BigDecimal amortizedAmount = capitalizedIncomeBalance.getAmount() //
.subtract(MathUtil.nullToZero(capitalizedIncomeBalance.getUnrecognizedAmount())) //
@@ -67,7 +68,8 @@ public class CapitalizedIncomeBalanceReadServiceImpl implements CapitalizedIncom
if (loanRepository.isEnabledCapitalizedIncome(loanId)) {
List<CapitalizedIncomeDetails> capitalizedIncomeData = new ArrayList<>();
List<LoanCapitalizedIncomeBalance> capitalizedIncomeBalances = capitalizedIncomeBalanceRepository.findAllByLoanId(loanId);
List<LoanCapitalizedIncomeBalance> capitalizedIncomeBalances = capitalizedIncomeBalanceRepository
.findAllByLoanIdAndDeletedFalseAndClosedFalse(loanId);
for (final LoanCapitalizedIncomeBalance capitalizedIncomeBalance : capitalizedIncomeBalances) {
final BigDecimal amortizedAmount = capitalizedIncomeBalance.getAmount() //
.subtract(MathUtil.nullToZero(capitalizedIncomeBalance.getUnrecognizedAmount())) //
@@ -159,8 +159,8 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
// Create journal entries immediately for this transaction
journalEntryPoster.postJournalEntriesForLoanTransaction(savedCapitalizedIncomeAdjustment, false, false);
LoanCapitalizedIncomeBalance capitalizedIncomeBalance = capitalizedIncomeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
capitalizedIncomeTransactionId);
LoanCapitalizedIncomeBalance capitalizedIncomeBalance = capitalizedIncomeBalanceRepository
.findByLoanIdAndLoanTransactionIdAndDeletedFalseAndClosedFalse(loanId, capitalizedIncomeTransactionId);
capitalizedIncomeBalance
.setAmountAdjustment(MathUtil.nullToZero(capitalizedIncomeBalance.getAmountAdjustment()).add(transactionAmount));
capitalizedIncomeBalance.setUnrecognizedAmount(
@@ -224,7 +224,7 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
}
if (transactionAmount != null) {
LoanCapitalizedIncomeBalance capitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository
.findByLoanIdAndLoanTransactionId(loanId, capitalizedIncomeTransactionId);
.findByLoanIdAndLoanTransactionIdAndDeletedFalseAndClosedFalse(loanId, capitalizedIncomeTransactionId);
if (MathUtil.isLessThan(capitalizedIncomeBalance.getAmount()
.subtract(MathUtil.nullToZero(capitalizedIncomeBalance.getAmountAdjustment())), transactionAmount)) {
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).failWithCode(
@@ -401,8 +401,8 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
}
if (transactionAmount != null) {
LoanBuyDownFeeBalance buydownFeeBalance = loanBuydownFeeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
buyDownFeeTransactionId);
LoanBuyDownFeeBalance buydownFeeBalance = loanBuydownFeeBalanceRepository
.findByLoanIdAndLoanTransactionIdAndDeletedFalseAndClosedFalse(loanId, buyDownFeeTransactionId);
if (buydownFeeBalance == null) {
baseDataValidator.reset().parameter("buyDownFeeTransactionId").failWithCode("buydown.fee.balance.not.found",
"Buy down fee balance not found for the specified transaction.");
@@ -123,6 +123,7 @@
<class>org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement</class>
<class>org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementDomain</class>
<class>org.apache.fineract.portfolio.collateral.domain.LoanCollateral</class>
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanAmortizationAllocationMapping</class>
<!-- Loan Module Converters -->
<class>org.apache.fineract.portfolio.loanproduct.domain.AllocationTypeListConverter</class>