FINERACT-2232: Capitalized Incomce Adjustment template

This commit is contained in:
Jose Alberto Hernandez
2025-05-24 01:17:55 +02:00
committed by Adam Saghy
parent f6e24d420a
commit 544607a05a
16 changed files with 221 additions and 24 deletions
@@ -35,7 +35,9 @@ public enum TransactionType {
INTEREST_PAYMENT_WAIVER("interestPaymentWaiver"), // INTEREST_PAYMENT_WAIVER("interestPaymentWaiver"), //
REPAYMENT_AT_DISBURSEMENT("repaymentAtDisbursement"), // REPAYMENT_AT_DISBURSEMENT("repaymentAtDisbursement"), //
CAPITALIZED_INCOME("capitalizedIncome"), // CAPITALIZED_INCOME("capitalizedIncome"), //
CAPITALIZED_INCOME_AMORTIZATION("capitalizedIncomeAmortization"); // CAPITALIZED_INCOME_AMORTIZATION("capitalizedIncomeAmortization"), //
CAPITALIZED_INCOME_ADJUSTMENT("capitalizedIncomeAdjustment"), //
;
public final String value; public final String value;
@@ -579,7 +579,7 @@ public class LoanRepaymentStepDef extends AbstractStepDef {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId1 = loanResponse.body().getLoanId(); long loanId1 = loanResponse.body().getLoanId();
Response<GetLoansLoanIdTransactionsTemplateResponse> response = loanTransactionsApi Response<GetLoansLoanIdTransactionsTemplateResponse> response = loanTransactionsApi
.retrieveTransactionTemplate(loanId1, "prepayLoan", DATE_FORMAT, transactionDate, DEFAULT_LOCALE).execute(); .retrieveTransactionTemplate(loanId1, "prepayLoan", DATE_FORMAT, transactionDate, DEFAULT_LOCALE, null).execute();
Double transactionAmount = response.body().getAmount(); Double transactionAmount = response.body().getAmount();
log.debug("%n--- Loan Pay-off with amount: {} ---", transactionAmount); log.debug("%n--- Loan Pay-off with amount: {} ---", transactionAmount);
@@ -175,6 +175,7 @@ public interface LoanApiConstants {
String CHARGEBACK_TRANSACTION_COMMAND = "chargeback"; String CHARGEBACK_TRANSACTION_COMMAND = "chargeback";
String MARK_AS_FRAUD_COMMAND = "markAsFraud"; String MARK_AS_FRAUD_COMMAND = "markAsFraud";
String CAPITALIZED_INCOME_TRANSACTION_COMMAND = "capitalizedIncome"; String CAPITALIZED_INCOME_TRANSACTION_COMMAND = "capitalizedIncome";
String CAPITALIZED_INCOME_ADJUSTMENT_TRANSACTION_COMMAND = "capitalizedIncomeAdjustment";
// Data Validator names // Data Validator names
String LOAN_FRAUD_DATAVALIDATOR_PREFIX = "loans.fraud"; String LOAN_FRAUD_DATAVALIDATOR_PREFIX = "loans.fraud";
@@ -54,5 +54,6 @@ public interface LoanTransactionApiConstants {
accrualAdjustment, // accrualAdjustment, //
capitalizedIncome, // capitalizedIncome, //
capitalizedIncomeAmortization, // capitalizedIncomeAmortization, //
capitalizedIncomeAdjustment, //
} }
} }
@@ -313,6 +313,36 @@ public class LoanTransactionData implements Serializable {
null, null, outstandingLoanBalance, null, manuallyReversed, ExternalId.empty(), null, loanId); null, null, outstandingLoanBalance, null, manuallyReversed, ExternalId.empty(), null, loanId);
} }
public static LoanTransactionData loanTransactionDataForCreditTemplate(final LoanTransactionEnumData transactionType,
final LocalDate transactionDate, final BigDecimal transactionAmount, final Collection<PaymentTypeData> paymentOptions,
final CurrencyData currency) {
final Long id = null;
final Long loanId = null;
final ExternalId externalLoanId = ExternalId.empty();
final ExternalId externalId = ExternalId.empty();
final Long officeId = null;
final String officeName = null;
final PaymentDetailData paymentDetailData = null;
final BigDecimal unrecognizedIncomePortion = null;
final BigDecimal principalPortion = null;
final BigDecimal interestPortion = null;
final BigDecimal feeChargesPortion = null;
final BigDecimal penaltyChargesPortion = null;
final BigDecimal overpaymentPortion = null;
final BigDecimal netDisbursalAmount = null;
final BigDecimal fixedEmiAmount = null;
final BigDecimal outstandingLoanBalance = null;
final AccountTransferData transfer = null;
final LocalDate submittedOnDate = null;
final LocalDate possibleNextRepaymentDate = null;
final boolean manuallyReversed = false;
return new LoanTransactionData(id, officeId, officeName, transactionType, paymentDetailData, currency, transactionDate,
transactionAmount, netDisbursalAmount, principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion,
overpaymentPortion, unrecognizedIncomePortion, paymentOptions, transfer, externalId, fixedEmiAmount, outstandingLoanBalance,
submittedOnDate, manuallyReversed, possibleNextRepaymentDate, loanId, externalLoanId);
}
public static LoanTransactionData loanTransactionDataForDisbursalTemplate(final LoanTransactionEnumData transactionType, public static LoanTransactionData loanTransactionDataForDisbursalTemplate(final LoanTransactionEnumData transactionType,
final LocalDate expectedDisbursedOnLocalDateForTemplate, final BigDecimal disburseAmountForTemplate, final LocalDate expectedDisbursedOnLocalDateForTemplate, final BigDecimal disburseAmountForTemplate,
final BigDecimal netDisbursalAmount, final Collection<PaymentTypeData> paymentOptions, final BigDecimal retriveLastEmiAmount, final BigDecimal netDisbursalAmount, final Collection<PaymentTypeData> paymentOptions, final BigDecimal retriveLastEmiAmount,
@@ -650,6 +650,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
return LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.equals(getTypeOf()) && isNotReversed(); return LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.equals(getTypeOf()) && isNotReversed();
} }
public boolean isCapitalizedIncomeAdjustment() {
return LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT.equals(getTypeOf()) && isNotReversed();
}
public boolean isWaiver() { public boolean isWaiver() {
return isInterestWaiver() || isChargesWaiver(); return isInterestWaiver() || isChargesWaiver();
} }
@@ -69,6 +69,7 @@ public enum LoanTransactionType {
ACCRUAL_ADJUSTMENT(34, "loanTransactionType.accrualAdjustment"), // ACCRUAL_ADJUSTMENT(34, "loanTransactionType.accrualAdjustment"), //
CAPITALIZED_INCOME(35, "loanTransactionType.capitalizedIncome"), // CAPITALIZED_INCOME(35, "loanTransactionType.capitalizedIncome"), //
CAPITALIZED_INCOME_AMORTIZATION(36, "loanTransactionType.capitalizedIncomeAmortization"), // CAPITALIZED_INCOME_AMORTIZATION(36, "loanTransactionType.capitalizedIncomeAmortization"), //
CAPITALIZED_INCOME_ADJUSTMENT(37, "loanTransactionType.capitalizedIncomeAdjustment"), //
; ;
private final Integer value; private final Integer value;
@@ -121,6 +122,7 @@ public enum LoanTransactionType {
case 34 -> LoanTransactionType.ACCRUAL_ADJUSTMENT; case 34 -> LoanTransactionType.ACCRUAL_ADJUSTMENT;
case 35 -> LoanTransactionType.CAPITALIZED_INCOME; case 35 -> LoanTransactionType.CAPITALIZED_INCOME;
case 36 -> LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION; case 36 -> LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION;
case 37 -> LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT;
default -> LoanTransactionType.INVALID; default -> LoanTransactionType.INVALID;
}; };
} }
@@ -241,4 +243,8 @@ public enum LoanTransactionType {
public boolean isCapitalizedIncome() { public boolean isCapitalizedIncome() {
return this == LoanTransactionType.CAPITALIZED_INCOME; return this == LoanTransactionType.CAPITALIZED_INCOME;
} }
public boolean isCapitalizedIncomeAdjustment() {
return this == LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT;
}
} }
@@ -63,7 +63,7 @@ public interface LoanReadPlatformService {
LoanTransactionData retrieveLoanTransactionTemplate(Long loanId); LoanTransactionData retrieveLoanTransactionTemplate(Long loanId);
LoanTransactionData retrieveLoanTransactionTemplate(Long loanId, LoanTransactionType transactionType); LoanTransactionData retrieveLoanTransactionTemplate(Long loanId, LoanTransactionType transactionType, Long transactionId);
LoanTransactionData retrieveWaiveInterestDetails(Long loanId); LoanTransactionData retrieveWaiveInterestDetails(Long loanId);
@@ -330,6 +330,9 @@ public final class LoanEnumerations {
case CAPITALIZED_INCOME_AMORTIZATION -> case CAPITALIZED_INCOME_AMORTIZATION ->
new LoanTransactionEnumData(LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.getValue().longValue(), new LoanTransactionEnumData(LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.getValue().longValue(),
LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.getCode(), "Capitalized Income Amortization"); LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.getCode(), "Capitalized Income Amortization");
case CAPITALIZED_INCOME_ADJUSTMENT ->
new LoanTransactionEnumData(LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT.getValue().longValue(),
LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT.getCode(), "Capitalized Income Adjustment");
}; };
} }
@@ -93,7 +93,10 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
// Post journal entries // Post journal entries
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
return new CommandProcessingResultBuilder().withEntityId(loan.getId()).withEntityExternalId(loan.getExternalId()).build(); return new CommandProcessingResultBuilder() //
.withEntityId(capitalizedIncomeTransaction.getId()) //
.withEntityExternalId(capitalizedIncomeTransaction.getExternalId()) //
.build();
} }
@Override @Override
@@ -91,6 +91,7 @@ public class LoanTransactionsApiResource {
public static final String REAMORTIZE = "reAmortize"; public static final String REAMORTIZE = "reAmortize";
public static final String UNDO_REAMORTIZE = "undoReAmortize"; public static final String UNDO_REAMORTIZE = "undoReAmortize";
public static final String CAPITALIZED_INCOME = "capitalizedIncome"; public static final String CAPITALIZED_INCOME = "capitalizedIncome";
public static final String CAPITALIZED_INCOME_ADJUSTMENT = "capitalizedIncomeAdjustment";
private final Set<String> responseDataParameters = new HashSet<>(Arrays.asList("id", "type", "date", "currency", "amount", "externalId", private final Set<String> responseDataParameters = new HashSet<>(Arrays.asList("id", "type", "date", "currency", "amount", "externalId",
LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME, LoanApiConstants.REVERSED_ON_DATE_PARAMNAME)); LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME, LoanApiConstants.REVERSED_ON_DATE_PARAMNAME));
@@ -126,11 +127,12 @@ public class LoanTransactionsApiResource {
@QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo, @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo,
@QueryParam("dateFormat") @Parameter(description = "dateFormat") final String rawDateFormat, @QueryParam("dateFormat") @Parameter(description = "dateFormat") final String rawDateFormat,
@QueryParam("transactionDate") @Parameter(description = "transactionDate") final DateParam transactionDateParam, @QueryParam("transactionDate") @Parameter(description = "transactionDate") final DateParam transactionDateParam,
@QueryParam("locale") @Parameter(description = "locale") final String locale) { @QueryParam("locale") @Parameter(description = "locale") final String locale,
@QueryParam("transactionId") @Parameter(description = "transactionId") final Long transactionId) {
final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat); final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat);
return retrieveTransactionTemplate(loanId, null, commandParam, uriInfo, dateFormat, transactionDateParam, locale); return retrieveTransactionTemplate(loanId, null, commandParam, uriInfo, dateFormat, transactionDateParam, locale, transactionId);
} }
@GET @GET
@@ -156,11 +158,13 @@ public class LoanTransactionsApiResource {
@QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo, @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo,
@QueryParam("dateFormat") @Parameter(description = "dateFormat") final String rawDateFormat, @QueryParam("dateFormat") @Parameter(description = "dateFormat") final String rawDateFormat,
@QueryParam("transactionDate") @Parameter(description = "transactionDate") final DateParam transactionDateParam, @QueryParam("transactionDate") @Parameter(description = "transactionDate") final DateParam transactionDateParam,
@QueryParam("locale") @Parameter(description = "locale") final String locale) { @QueryParam("locale") @Parameter(description = "locale") final String locale,
@QueryParam("transactionId") @Parameter(description = "transactionId") final Long transactionId) {
final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat); final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat);
return retrieveTransactionTemplate(null, loanExternalId, commandParam, uriInfo, dateFormat, transactionDateParam, locale); return retrieveTransactionTemplate(null, loanExternalId, commandParam, uriInfo, dateFormat, transactionDateParam, locale,
transactionId);
} }
@GET @GET
@@ -529,6 +533,7 @@ public class LoanTransactionsApiResource {
case accrualAdjustment -> LoanTransactionType.ACCRUAL_ADJUSTMENT; case accrualAdjustment -> LoanTransactionType.ACCRUAL_ADJUSTMENT;
case capitalizedIncome -> LoanTransactionType.CAPITALIZED_INCOME; case capitalizedIncome -> LoanTransactionType.CAPITALIZED_INCOME;
case capitalizedIncomeAmortization -> LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION; case capitalizedIncomeAmortization -> LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION;
case capitalizedIncomeAdjustment -> LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT;
default -> default ->
throw new InvalidLoanTransactionTypeException("transaction", transactionTypeParam.name(), "Unknown transaction type"); throw new InvalidLoanTransactionTypeException("transaction", transactionTypeParam.name(), "Unknown transaction type");
}; };
@@ -598,7 +603,7 @@ public class LoanTransactionsApiResource {
} }
private String retrieveTransactionTemplate(Long loanId, String loanExternalIdStr, String commandParam, UriInfo uriInfo, private String retrieveTransactionTemplate(Long loanId, String loanExternalIdStr, String commandParam, UriInfo uriInfo,
DateFormat dateFormat, DateParam transactionDateParam, String locale) { DateFormat dateFormat, DateParam transactionDateParam, String locale, Long transactionId) {
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr); ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
@@ -671,7 +676,10 @@ public class LoanTransactionsApiResource {
transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId); transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId);
} else if (CommandParameterUtil.is(commandParam, LoanApiConstants.CAPITALIZED_INCOME_TRANSACTION_COMMAND)) { } else if (CommandParameterUtil.is(commandParam, LoanApiConstants.CAPITALIZED_INCOME_TRANSACTION_COMMAND)) {
transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId, transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId,
LoanTransactionType.CAPITALIZED_INCOME); LoanTransactionType.CAPITALIZED_INCOME, transactionId);
} else if (CommandParameterUtil.is(commandParam, LoanApiConstants.CAPITALIZED_INCOME_ADJUSTMENT_TRANSACTION_COMMAND)) {
transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId,
LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT, transactionId);
} else { } else {
throw new UnrecognizedQueryParamException("command", commandParam); throw new UnrecognizedQueryParamException("command", commandParam);
} }
@@ -50,6 +50,7 @@ import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.PaginationHelper; import org.apache.fineract.infrastructure.core.service.PaginationHelper;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
@@ -107,6 +108,7 @@ import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData; import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType; import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy; import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType; import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -126,6 +128,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanSc
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionMapper; import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionMapper;
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanForeclosureValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanForeclosureValidator;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData; import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
@@ -180,6 +183,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
private final LoanTransactionMapper loanTransactionMapper; private final LoanTransactionMapper loanTransactionMapper;
private final LoanTransactionProcessingService loadTransactionProcessingService; private final LoanTransactionProcessingService loadTransactionProcessingService;
private final LoanBalanceService loanBalanceService; private final LoanBalanceService loanBalanceService;
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
@Override @Override
public LoanAccountData retrieveOne(final Long loanId) { public LoanAccountData retrieveOne(final Long loanId) {
@@ -451,10 +455,52 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
return loanDetails; return loanDetails;
} }
private CurrencyData retriveLoanCurrencyData(final Long loanId) {
final LoanCurrencyDataMapper loanCurrencyMapper = new LoanCurrencyDataMapper(sqlGenerator);
final String sql = "select " + loanCurrencyMapper.schema() + " where l.id = ?";
return this.jdbcTemplate.queryForObject(sql, loanCurrencyMapper, loanId);
}
@Override @Override
public LoanTransactionData retrieveLoanTransactionTemplate(final Long loanId, LoanTransactionType transactionType) { public LoanTransactionData retrieveLoanTransactionTemplate(final Long loanId, final LoanTransactionType transactionType,
return LoanTransactionData.templateOnTop(retrieveLoanTransactionTemplate(loanId), final Long transactionId) {
LoanEnumerations.transactionType(transactionType));
LoanTransactionData loanTransactionData = null;
Collection<PaymentTypeData> paymentOptions = null;
BigDecimal transactionAmount = BigDecimal.ZERO;
switch (transactionType) {
case CAPITALIZED_INCOME:
final Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
if (loan.getLoanProduct().getLoanProductRelatedDetail().isEnableIncomeCapitalization()) {
final BigDecimal capitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loanId).stream()
.map(LoanCapitalizedIncomeBalance::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
transactionAmount = loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount()).subtract(capitalizedIncomeBalance);
}
paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
loanTransactionData = LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType), DateUtils.getBusinessLocalDate(), transactionAmount,
paymentOptions, retriveLoanCurrencyData(loanId));
break;
case CAPITALIZED_INCOME_ADJUSTMENT:
final LoanCapitalizedIncomeBalance loanCapitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository
.findByLoanIdAndLoanTransactionId(loanId, transactionId);
transactionAmount = (loanCapitalizedIncomeBalance == null) ? BigDecimal.ZERO
: loanCapitalizedIncomeBalance.getAmount()
.subtract(MathUtil.nullToZero(loanCapitalizedIncomeBalance.getAmountAdjustment()));
loanTransactionData = LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType), DateUtils.getBusinessLocalDate(), transactionAmount,
paymentOptions, retriveLoanCurrencyData(loanId));
break;
default:
loanTransactionData = LoanTransactionData.templateOnTop(retrieveLoanTransactionTemplate(loanId),
LoanEnumerations.transactionType(transactionType));
break;
}
return loanTransactionData;
} }
@Override @Override
@@ -646,6 +692,33 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
return loanId; return loanId;
} }
private static final class LoanCurrencyDataMapper implements RowMapper<CurrencyData> {
private final DatabaseSpecificSQLGenerator sqlGenerator;
LoanCurrencyDataMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
this.sqlGenerator = sqlGenerator;
}
public String schema() {
return " l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, rc."
+ sqlGenerator.escape("name")
+ " as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode from m_loan l join m_currency rc on rc."
+ sqlGenerator.escape("code") + " = l.currency_code ";
}
@Override
public CurrencyData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
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");
return new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, currencyNameCode);
}
}
private static final class LoanMapper implements RowMapper<LoanAccountData> { private static final class LoanMapper implements RowMapper<LoanAccountData> {
private final DatabaseSpecificSQLGenerator sqlGenerator; private final DatabaseSpecificSQLGenerator sqlGenerator;
@@ -88,6 +88,7 @@ import org.apache.fineract.portfolio.loanaccount.mapper.LoanChargeMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanCollateralManagementMapper; import org.apache.fineract.portfolio.loanaccount.mapper.LoanCollateralManagementMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanMapper; import org.apache.fineract.portfolio.loanaccount.mapper.LoanMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionMapper; import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionMapper;
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
@@ -333,14 +334,16 @@ public class LoanAccountConfiguration {
DelinquencyReadPlatformService delinquencyReadPlatformService, LoanTransactionRepository loanTransactionRepository, DelinquencyReadPlatformService delinquencyReadPlatformService, LoanTransactionRepository loanTransactionRepository,
LoanChargePaidByReadService loanChargePaidByReadService, LoanTransactionRelationReadService loanTransactionRelationReadService, LoanChargePaidByReadService loanChargePaidByReadService, LoanTransactionRelationReadService loanTransactionRelationReadService,
LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper, LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper,
LoanTransactionProcessingService loanTransactionProcessingService, LoanBalanceService loanBalanceService) { LoanTransactionProcessingService loanTransactionProcessingService, LoanBalanceService loanBalanceService,
LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository) {
return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository, return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository,
loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService, loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService,
fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService, fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService,
staffReadPlatformService, paginationHelper, paymentTypeReadPlatformService, floatingRatesReadPlatformService, staffReadPlatformService, paginationHelper, paymentTypeReadPlatformService, floatingRatesReadPlatformService,
loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator, loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator,
delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService, delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService,
loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService); loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService,
loanCapitalizedIncomeBalanceRepository);
} }
@Bean @Bean
@@ -276,7 +276,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
} }
protected GetLoansLoanIdTransactionsTemplateResponse getPrepayAmount(Long loanId, String date) { protected GetLoansLoanIdTransactionsTemplateResponse getPrepayAmount(Long loanId, String date) {
return ok(fineractClient().loanTransactions.retrieveTransactionTemplate(loanId, "prepayLoan", DATETIME_PATTERN, date, "en")); return ok(fineractClient().loanTransactions.retrieveTransactionTemplate(loanId, "prepayLoan", DATETIME_PATTERN, date, "en", null));
} }
protected Long verifyPrepayAmountByRepayment(Long loanId, String date) { protected Long verifyPrepayAmountByRepayment(Long loanId, String date) {
@@ -25,17 +25,25 @@ import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsResponse; import org.apache.fineract.client.models.GetLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse; import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
import org.apache.fineract.client.models.PostClientsResponse; import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse; import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.TransactionType; import org.apache.fineract.client.models.TransactionType;
import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.ClientHelper;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@Slf4j
public class LoanTransactionTest extends BaseLoanIntegrationTest { public class LoanTransactionTest extends BaseLoanIntegrationTest {
private final String capitalizedIncomeCommand = "capitalizedIncome";
private final String capitalizedIncomeAdjustmentCommand = "capitalizedIncomeAdjustment";
@Test @Test
public void testGetLoanTransactionsFiltering() { public void testGetLoanTransactionsFiltering() {
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()); final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
@@ -94,24 +102,68 @@ public class LoanTransactionTest extends BaseLoanIntegrationTest {
public void testGetLoanTransactionTemplateForCapitalizedIncome() { public void testGetLoanTransactionTemplateForCapitalizedIncome() {
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()); final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive()); final PostLoanProductsResponse loanProductsResponse = loanProductHelper
.createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)
.deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue())
.incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue())
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE));
final String loanExternalIdStr = UUID.randomUUID().toString(); final String loanExternalIdStr = UUID.randomUUID().toString();
final String command = "capitalizedIncome";
runAt("20 December 2024", () -> { runAt("20 December 2024", () -> {
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "20 December 2024", Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "20 December 2024",
430.0, 7.0, 6, (request) -> request.externalId(loanExternalIdStr)); 430.0, 7.0, 6, (request) -> request.externalId(loanExternalIdStr));
disburseLoan(loanId, BigDecimal.valueOf(430), "20 December 2024"); disburseLoan(loanId, BigDecimal.valueOf(230), "20 December 2024");
final GetLoansLoanIdTransactionsTemplateResponse transactionTemplate = loanTransactionHelper.retrieveTransactionTemplate(loanId, final GetLoansLoanIdTransactionsTemplateResponse transactionTemplate = loanTransactionHelper.retrieveTransactionTemplate(loanId,
command, null, null, null); capitalizedIncomeCommand, null, null, null);
assertNotNull(transactionTemplate); assertNotNull(transactionTemplate);
assertEquals("loanTransactionType." + command, transactionTemplate.getType().getCode()); assertEquals("loanTransactionType." + capitalizedIncomeCommand, transactionTemplate.getType().getCode());
assertEquals(transactionTemplate.getAmount(), 200);
assertThat(transactionTemplate.getPaymentTypeOptions().size() > 0); assertThat(transactionTemplate.getPaymentTypeOptions().size() > 0);
}); });
} }
@Test
public void testGetLoanTransactionTemplateForCapitalizedIncomeAdjustment() {
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
final PostLoanProductsResponse loanProductsResponse = loanProductHelper
.createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)
.deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue())
.incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue())
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE));
final String loanExternalIdStr = UUID.randomUUID().toString();
runAt("20 December 2024", () -> {
final Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(),
"20 December 2024", 430.0, 7.0, 6, (request) -> request.externalId(loanExternalIdStr));
disburseLoan(loanId, BigDecimal.valueOf(230), "20 December 2024");
PostLoansLoanIdTransactionsResponse loanTransactionResponse = loanTransactionHelper.executeLoanTransaction(loanId,
new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("20 December 2024").locale("en")
.transactionAmount(150.0),
capitalizedIncomeCommand);
assertNotNull(loanTransactionResponse);
final Long transactionId = loanTransactionResponse.getResourceId();
assertNotNull(transactionId);
log.info("Loan Id {} with transaction id {}", loanId, transactionId);
final GetLoansLoanIdTransactionsTemplateResponse transactionTemplate = loanTransactionHelper.retrieveTransactionTemplate(loanId,
capitalizedIncomeAdjustmentCommand, null, null, null, transactionId);
assertNotNull(transactionTemplate);
assertEquals("loanTransactionType." + capitalizedIncomeAdjustmentCommand, transactionTemplate.getType().getCode());
assertEquals(transactionTemplate.getAmount(), 150);
});
}
} }
@@ -902,6 +902,11 @@ public class LoanTransactionHelper {
return postLoanTransaction(createLoanTransactionURL(MAKE_REPAYMENT_COMMAND, loanID), getRepaymentBodyAsJSON(date, amountToBePaid)); return postLoanTransaction(createLoanTransactionURL(MAKE_REPAYMENT_COMMAND, loanID), getRepaymentBodyAsJSON(date, amountToBePaid));
} }
public PostLoansLoanIdTransactionsResponse executeLoanTransaction(final Long loanId, final PostLoansLoanIdTransactionsRequest request,
final String command) {
return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction(loanId, request, command));
}
public PostLoansLoanIdTransactionsResponse makeLoanRepayment(final Long loanId, final PostLoansLoanIdTransactionsRequest request) { public PostLoansLoanIdTransactionsResponse makeLoanRepayment(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction(loanId, request, "repayment")); return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction(loanId, request, "repayment"));
} }
@@ -2332,7 +2337,7 @@ public class LoanTransactionHelper {
public GetLoansLoanIdTransactionsTemplateResponse getPrepaymentAmount(final Long loanId, final String transactionDate, public GetLoansLoanIdTransactionsTemplateResponse getPrepaymentAmount(final Long loanId, final String transactionDate,
String dateformat) { String dateformat) {
return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.retrieveTransactionTemplate(loanId, "prepayLoan", return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.retrieveTransactionTemplate(loanId, "prepayLoan",
dateformat, transactionDate, "en")); dateformat, transactionDate, "en", null));
} }
// TODO: Rewrite to use fineract-client instead! // TODO: Rewrite to use fineract-client instead!
@@ -2806,16 +2811,22 @@ public class LoanTransactionHelper {
return chargebackPayload; return chargebackPayload;
} }
public GetLoansLoanIdTransactionsTemplateResponse retrieveTransactionTemplate(Long loanId, String command, String dateFormat,
String transactionDate, String locale, Long transactionId) {
return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.retrieveTransactionTemplate(loanId, command, dateFormat,
transactionDate, locale, transactionId));
}
public GetLoansLoanIdTransactionsTemplateResponse retrieveTransactionTemplate(Long loanId, String command, String dateFormat, public GetLoansLoanIdTransactionsTemplateResponse retrieveTransactionTemplate(Long loanId, String command, String dateFormat,
String transactionDate, String locale) { String transactionDate, String locale) {
return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.retrieveTransactionTemplate(loanId, command, dateFormat, return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.retrieveTransactionTemplate(loanId, command, dateFormat,
transactionDate, locale)); transactionDate, locale, null));
} }
public GetLoansLoanIdTransactionsTemplateResponse retrieveTransactionTemplate(String loanExternalIdStr, String command, public GetLoansLoanIdTransactionsTemplateResponse retrieveTransactionTemplate(String loanExternalIdStr, String command,
String dateFormat, String transactionDate, String locale) { String dateFormat, String transactionDate, String locale) {
return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, command, return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, command,
dateFormat, transactionDate, locale)); dateFormat, transactionDate, locale, null));
} }
public GetLoansApprovalTemplateResponse getLoanApprovalTemplate(String loanExternalIdStr) { public GetLoansApprovalTemplateResponse getLoanApprovalTemplate(String loanExternalIdStr) {