FINERACT-2347: Introduce classification field on loan transactions

This commit is contained in:
Jose Alberto Hernandez
2025-09-03 11:43:06 +02:00
committed by Adam Saghy
parent 9ba17bf79a
commit 65538495aa
27 changed files with 442 additions and 21 deletions
@@ -123,6 +123,23 @@ public class CodesApiResource {
return this.toApiJsonSerializer.serialize(settings, code, RESPONSE_DATA_PARAMETERS);
}
@GET
@Path("name/{codeName}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a Code", description = "Returns the details of a Code.\n" + "\n" + "Example Requests:\n" + "\n"
+ "codes/1")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CodesApiResourceSwagger.GetCodesResponse.class))) })
public String retrieveCodeByName(@PathParam("codeName") @Parameter(description = "codeName") final String codeName,
@Context final UriInfo uriInfo) {
final CodeData code = this.readPlatformService.retriveCode(codeName);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, code, RESPONSE_DATA_PARAMETERS);
}
@PUT
@Path("{codeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@@ -93,7 +93,7 @@ public class LoanTransactionsApiResource {
public static final String CAPITALIZED_INCOME = "capitalizedIncome";
public static final String INTEREST_REFUND_COMMAND_VALUE = "interest-refund";
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, "classification"));
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN";
@@ -35,6 +35,8 @@ import java.util.Set;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
@@ -67,6 +69,7 @@ import org.apache.fineract.portfolio.common.service.Validator;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -107,6 +110,7 @@ public final class LoanTransactionValidatorImpl implements LoanTransactionValida
private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator;
private final LoanDisbursementValidator loanDisbursementValidator;
private final CodeValueRepository codeValueRepository;
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
@@ -1120,4 +1124,18 @@ public final class LoanTransactionValidatorImpl implements LoanTransactionValida
validatePaymentDetails(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@Override
public void validateClassificationCodeValue(final String codeName, final Long transactionClassificationId,
DataValidatorBuilder baseDataValidator) {
baseDataValidator.reset().parameter(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME)
.value(transactionClassificationId).ignoreIfNull().positiveAmount();
if (transactionClassificationId != null) {
final CodeValue codeValue = codeValueRepository.findByCodeNameAndId(codeName, transactionClassificationId);
if (codeValue == null) {
baseDataValidator.reset().parameter(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME)
.failWithCode("code.value.classification.not.exists", "Code value does not exists in the code " + codeName);
}
}
}
}
@@ -94,6 +94,7 @@ import org.apache.fineract.portfolio.group.data.GroupGeneralData;
import org.apache.fineract.portfolio.group.data.GroupRoleData;
import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData;
@@ -485,6 +486,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
LoanTransactionData loanTransactionData = null;
Collection<PaymentTypeData> paymentOptions = null;
List<CodeValueData> classificationOptions = null;
BigDecimal transactionAmount = BigDecimal.ZERO;
switch (transactionType) {
case CAPITALIZED_INCOME:
@@ -499,15 +501,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
: loan.getApprovedPrincipal();
transactionAmount = transactionAmount.subtract(loan.getDisbursedAmount()).subtract(capitalizedIncomeBalance);
paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
classificationOptions = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE);
loanTransactionData = LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType), DateUtils.getBusinessLocalDate(), transactionAmount,
paymentOptions, retriveLoanCurrencyData(loanId));
paymentOptions, retriveLoanCurrencyData(loanId), classificationOptions);
break;
case BUY_DOWN_FEE:
paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
classificationOptions = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(LoanTransactionApiConstants.BUY_DOWN_FEE_CLASSIFICATION_CODE);
loanTransactionData = LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType), DateUtils.getBusinessLocalDate(), transactionAmount,
paymentOptions, retriveLoanCurrencyData(loanId));
paymentOptions, retriveLoanCurrencyData(loanId), classificationOptions);
break;
case CAPITALIZED_INCOME_ADJUSTMENT:
final LoanCapitalizedIncomeBalance loanCapitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository
@@ -518,7 +524,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
.subtract(MathUtil.nullToZero(loanCapitalizedIncomeBalance.getAmountAdjustment()));
loanTransactionData = LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType), DateUtils.getBusinessLocalDate(), transactionAmount,
paymentOptions, retriveLoanCurrencyData(loanId));
paymentOptions, retriveLoanCurrencyData(loanId), classificationOptions);
break;
case BUY_DOWN_FEE_ADJUSTMENT:
final LoanBuyDownFeeBalance loanBuyDownFeeBalance = loanBuyDownFeeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
@@ -528,7 +534,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
: loanBuyDownFeeBalance.getAmount().subtract(MathUtil.nullToZero(loanBuyDownFeeBalance.getAmountAdjustment()));
loanTransactionData = LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType), DateUtils.getBusinessLocalDate(), transactionAmount,
paymentOptions, retriveLoanCurrencyData(loanId));
paymentOptions, retriveLoanCurrencyData(loanId), classificationOptions);
break;
default:
loanTransactionData = LoanTransactionData.templateOnTop(retrieveLoanTransactionTemplate(loanId),
@@ -1667,13 +1673,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
+ " fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount,"
+ " fromtran.description as fromTransferDescription, "
+ " totran.id as toTransferId, totran.is_reversed as toTransferReversed, "
+ " totran.transaction_date as toTransferDate, totran.amount as toTransferAmount,"
+ " totran.transaction_date as toTransferDate, totran.amount as toTransferAmount, "
+ " clcv.id as classCodeId, clcv.code_value as classCodeValue, "
+ " totran.description as toTransferDescription from m_loan l join m_loan_transaction tr on tr.loan_id = l.id "
+ " join m_currency rc on rc." + sqlGenerator.escape("code") + " = l.currency_code "
+ " left JOIN m_payment_detail pd ON tr.payment_detail_id = pd.id"
+ " left join m_payment_type pt on pd.payment_type_id = pt.id left join m_office office on office.id=tr.office_id"
+ " left join m_account_transfer_transaction fromtran on fromtran.from_loan_transaction_id = tr.id "
+ " left join m_account_transfer_transaction totran on totran.to_loan_transaction_id = tr.id ";
+ " left join m_account_transfer_transaction totran on totran.to_loan_transaction_id = tr.id "
+ " left join m_code_value clcv on clcv.id = tr.classification_cv_id ";
}
@Override
@@ -1700,6 +1708,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
PaymentDetailData paymentDetailData = null;
final CodeValueData classificationData = CodeValueData.instance(rs.getLong("classCodeId"), rs.getString("classCodeValue"));
final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType");
if (paymentTypeId != null) {
final String typeName = rs.getString("paymentTypeName");
@@ -1759,7 +1769,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
.overpaymentPortion(overPaymentPortion).unrecognizedIncomePortion(unrecognizedIncomePortion).externalId(externalId)
.transfer(transfer).outstandingLoanBalance(outstandingLoanBalance).submittedOnDate(submittedOnDate)
.manuallyReversed(manuallyReversed).reversalExternalId(reversalExternalId).reversedOnDate(reversedOnDate).loanId(loanId)
.externalLoanId(externalLoanId).build();
.externalLoanId(externalLoanId).classification(classificationData).build();
}
}
@@ -2491,8 +2501,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
final Collection<PaymentTypeData> paymentTypeOptions = paymentTypeReadPlatformService.retrieveAllPaymentTypes();
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.INTEREST_REFUND);
return LoanTransactionData.loanTransactionDataForCreditTemplate(transactionType, targetTxn.getTransactionDate(),
interestRefundAmount, paymentTypeOptions, loan.getCurrency().toData());
return LoanTransactionData.builder().transactionType(LoanTransactionType.INTEREST_REFUND.name()).type(transactionType)
.date(targetTxn.getTransactionDate()).amount(interestRefundAmount).paymentTypeOptions(paymentTypeOptions)
.currency(loan.getCurrency().toData()).build();
}
@Override
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.starter;
import org.apache.fineract.cob.service.LoanAccountLockService;
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
@@ -391,10 +392,11 @@ public class LoanAccountConfiguration {
LoanAssembler loanAssembler, LoanTransactionRepository loanTransactionRepository,
PaymentDetailWritePlatformService paymentDetailWritePlatformService, LoanJournalEntryPoster loanJournalEntryPoster,
NoteWritePlatformService noteWritePlatformService, ExternalIdFactory externalIdFactory,
LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository, BusinessEventNotifierService businessEventNotifierService) {
LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository, BusinessEventNotifierService businessEventNotifierService,
CodeValueRepository codeValueRepository) {
return new BuyDownFeeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, loanTransactionRepository,
paymentDetailWritePlatformService, loanJournalEntryPoster, noteWritePlatformService, externalIdFactory,
loanBuyDownFeeBalanceRepository, businessEventNotifierService);
loanBuyDownFeeBalanceRepository, businessEventNotifierService, codeValueRepository);
}
@Bean