FINERACT-2327: Allow to create Interest Refund transaction manually

This commit is contained in:
mariiaKraievska
2025-07-23 11:32:30 +02:00
committed by Aleksandar Vidakovic
parent 59409550a7
commit c2e19bb68f
22 changed files with 799 additions and 8 deletions
@@ -93,6 +93,7 @@ public class LoanTransactionsApiResource {
public static final String CAPITALIZED_INCOME = "capitalizedIncome";
public static final String CAPITALIZED_INCOME_ADJUSTMENT = "capitalizedIncomeAdjustment";
public static final String CONTRACT_TERMINATION = "contractTermination";
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));
@@ -121,7 +122,8 @@ public class LoanTransactionsApiResource {
+ "loans/1/transactions/template?command=refundbycash" + "\n" + "loans/1/transactions/template?command=refundbytransfer" + "\n"
+ "loans/1/transactions/template?command=foreclosure" + "\n" + "loans/1/transactions/template?command=interestPaymentWaiver"
+ "\n" + "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)"
+ "\n" + "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n")
+ "\n" + "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n"
+ "loans/1/transactions/template?command=interest-refund")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) })
public String retrieveTransactionTemplate(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
@@ -151,7 +153,8 @@ public class LoanTransactionsApiResource {
+ "loans/1/transactions/template?command=refundbycash" + "\n" + "loans/1/transactions/template?command=refundbytransfer" + "\n"
+ "loans/1/transactions/template?command=foreclosure" + "\n" + "loans/1/transactions/template?command=interestPaymentWaiver"
+ "\n" + "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)"
+ "\n" + "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n")
+ "\n" + "loans/1/transactions/template?command=charge-off" + "\n" + "loans/1/transactions/template?command=downPayment" + "\n"
+ "loans/1/transactions/template?command=interest-refund")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) })
public String retrieveTransactionTemplate(
@@ -694,6 +697,8 @@ public class LoanTransactionsApiResource {
} else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) {
transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId,
LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT, transactionId);
} else if (CommandParameterUtil.is(commandParam, INTEREST_REFUND_COMMAND_VALUE)) {
transactionData = this.loanReadPlatformService.retrieveManualInterestRefundTemplate(resolvedLoanId, transactionId);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
@@ -718,6 +723,8 @@ public class LoanTransactionsApiResource {
commandRequest = builder.capitalizedIncomeAdjustment(resolvedLoanId, resolvedTransactionId).build();
} else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) {
commandRequest = builder.buyDownFeeAdjustment(resolvedLoanId, resolvedTransactionId).build();
} else if (CommandParameterUtil.is(commandParam, INTEREST_REFUND_COMMAND_VALUE)) {
commandRequest = builder.manualInterestRefund(resolvedLoanId, resolvedTransactionId).build();
} else { // Default to adjust the Loan Transaction
commandRequest = builder.adjustTransaction(resolvedLoanId, resolvedTransactionId).build();
}
@@ -206,7 +206,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final ExternalId txnExternalId = externalIdFactory.create();
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestRefundPreBusinessEvent(loan));
return LoanTransaction.interestRefund(loan, interestRefundAmount, refundTransaction.getDateOf(), txnExternalId);
}
@Override
public LoanTransaction createManualInterestRefundWithAmount(final Loan loan, final LoanTransaction targetTransaction,
final BigDecimal interestRefundAmount, final PaymentDetail paymentDetail, final ExternalId txnExternalId) {
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestRefundPreBusinessEvent(loan));
return LoanTransaction.interestRefund(loan, interestRefundAmount, targetTransaction.getDateOf(), paymentDetail, txnExternalId);
}
@Transactional
@@ -1089,4 +1089,35 @@ public final class LoanTransactionValidatorImpl implements LoanTransactionValida
baseDataValidator.reset().parameter("reversalExternalId").value(reversalExternalId).notExceedingLengthOf(100);
}
}
@Override
public void validateManualInterestRefundTransaction(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Set<String> transactionParameters = new HashSet<>(
Arrays.asList("transactionAmount", "externalId", "note", "locale", "dateFormat", "paymentTypeId", "accountNumber",
"checkNumber", "routingCode", "receiptNumber", "bankNumber", "loanId", "numberOfRepayments"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, transactionParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
baseDataValidator.reset().parameter("externalId").value(externalId).ignoreIfNull().notExceedingLengthOf(100);
validatePaymentDetails(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
}
@@ -46,8 +46,10 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
import org.apache.fineract.infrastructure.core.api.ApiFacingEnum;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.MathUtil;
@@ -192,6 +194,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
private final LoanBalanceService loanBalanceService;
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
private final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository;
private final InterestRefundServiceDelegate interestRefundServiceDelegate;
@Override
public LoanAccountData retrieveOne(final Long loanId) {
@@ -2448,6 +2451,39 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
return loanRepositoryWrapper.existsByLoanId(loanId);
}
@Override
public LoanTransactionData retrieveManualInterestRefundTemplate(final Long loanId, final Long targetTransactionId) {
final Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
final LoanTransaction targetTxn = loan.getLoanTransaction(txn -> txn.getId() != null && txn.getId().equals(targetTransactionId));
if (targetTxn == null) {
throw new LoanTransactionNotFoundException(targetTransactionId);
}
if (!(targetTxn.isMerchantIssuedRefund() || targetTxn.isPayoutRefund())) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.not.refund.type", "Only for refund transactions");
}
if (targetTxn.isReversed()) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.reversed", "Refund transaction is reversed");
}
final boolean alreadyExists = loan.getLoanTransactions().stream().anyMatch(txn -> txn.isInterestRefund() && !txn
.getLoanTransactionRelations(rel -> rel.getToTransaction() != null && rel.getToTransaction().equals(targetTxn)).isEmpty());
if (alreadyExists) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.already.exists",
"Interest Refund already exists for this refund");
}
final InterestRefundService interestRefundService = interestRefundServiceDelegate.lookupInterestRefundService(loan);
final Money totalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), targetTxn.getTransactionDate(),
List.of(), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList());
final Money newTotalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), targetTxn.getTransactionDate(),
List.of(targetTxn), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList());
final BigDecimal interestRefundAmount = totalInterest.minus(newTotalInterest).getAmount();
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());
}
private LoanTransaction deriveDefaultInterestWaiverTransaction(final Loan loan) {
final Money totalInterestOutstanding = loan.getTotalInterestOutstandingOnLoan();
Money possibleInterestToWaive = totalInterestOutstanding.copy();
@@ -98,6 +98,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPreBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestRefundPostBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoChargeOffBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoWrittenOffBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWaiveInterestBusinessEvent;
@@ -188,6 +189,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactio
import org.apache.fineract.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanForeclosureException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanMultiDisbursementException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
@@ -210,6 +212,7 @@ import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommand
import org.apache.fineract.portfolio.loanaccount.service.adjustment.LoanAdjustmentParameter;
import org.apache.fineract.portfolio.loanaccount.service.adjustment.LoanAdjustmentService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.note.domain.Note;
@@ -3010,6 +3013,136 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build();
}
@Transactional
@Override
public CommandProcessingResult makeManualInterestRefund(final Long loanId, final Long transactionId, final JsonCommand command) {
loanTransactionValidator.validateManualInterestRefundTransaction(command.json());
Loan loan = loanAssembler.assembleFrom(loanId);
if (loan == null) {
throw new LoanNotFoundException(loanId);
}
final LoanTransaction targetTransaction = this.loanTransactionRepository.findByIdAndLoanId(transactionId, loanId)
.orElseThrow(() -> new LoanTransactionNotFoundException(transactionId, loanId));
final LocalDate transactionDate = targetTransaction.getDateOf();
if (!(targetTransaction.isMerchantIssuedRefund() || targetTransaction.isPayoutRefund())) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.not.refund.type",
"Target transaction must be Merchant Issued Refund or Payout Refund");
}
if (targetTransaction.isReversed()) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.reversed", "Target transaction is already reversed");
}
boolean alreadyHasInterestRefund = loan.getLoanTransactions().stream()
.anyMatch(txn -> txn.isInterestRefund()
&& !txn.getLoanTransactionRelations(rel -> rel.getToTransaction().equals(targetTransaction)).isEmpty()
&& !txn.isReversed());
if (alreadyHasInterestRefund) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.already.exists",
"Interest Refund already exists for this transaction");
}
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("transactionAmount");
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.amount.invalid",
"Amount must be provided and positive");
}
final boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
.map(LoanSupportedInterestRefundTypes::getTransactionType)
.anyMatch(transactionType -> transactionType.equals(targetTransaction.getTypeOf()));
if (!shouldCreateInterestRefundTransaction) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.interest.refund.not.supported",
"Interest Refund calculation is not supported for this loan");
}
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
final Map<String, Object> changes = new LinkedHashMap<>();
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
changes.put(LoanApiConstants.externalIdParameterName, txnExternalId);
final LocalDate recalculateFrom = loan.isInterestBearingAndInterestRecalculationEnabled() ? transactionDate : null;
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, null);
this.loanTransactionValidator.validateRefund(loan, LoanTransactionType.INTEREST_REFUND, transactionDate, scheduleGeneratorDTO);
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
createNote(loan, command, changes);
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
final LoanTransaction interestRefundTxn = loanAccountDomainService.createManualInterestRefundWithAmount(loan, targetTransaction,
amount, paymentDetail, txnExternalId);
interestRefundTxn.getLoanTransactionRelations().add(
LoanTransactionRelation.linkToTransaction(interestRefundTxn, targetTransaction, LoanTransactionRelationTypeEnum.RELATED));
final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan,
interestRefundTxn);
final LoanRepaymentScheduleInstallment currentInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate);
final boolean processLatest = isTransactionChronologicallyLatest //
&& !loan.isForeclosure() //
&& !loan.hasChargesAffectedByBackdatedRepaymentLikeTransaction(interestRefundTxn) //
&& loanTransactionProcessingService.canProcessLatestTransactionOnly(loan, interestRefundTxn, currentInstallment); //
if (processLatest) {
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), interestRefundTxn,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
loan.addLoanTransaction(interestRefundTxn);
} else {
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
loan.addLoanTransaction(interestRefundTxn);
reprocessLoanTransactionsService.reprocessTransactions(loan);
}
// Update outstanding loan balances
loanBalanceService.updateLoanOutstandingBalances(loan);
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTxn);
businessEventNotifierService.notifyPostBusinessEvent(new LoanTransactionInterestRefundPostBusinessEvent(interestRefundTxn));
// Accrual reprocessing
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
}
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate);
loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
loanAccountDomainService.updateAndSavePostDatedChecksForIndividualAccount(loan, interestRefundTxn);
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, interestRefundTxn);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withLoanId(loan.getId()) //
.withEntityId(targetTransaction.getId()) //
.withEntityExternalId(targetTransaction.getExternalId()) //
.withSubEntityId(interestRefundTxn.getId()) //
.withSubEntityExternalId(interestRefundTxn.getExternalId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.with(changes) //
.build();
}
public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction) {
loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction);
@@ -3626,5 +3759,4 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.setExpectedMaturityDate(latestRepaymentDate);
}
}
}
@@ -110,6 +110,7 @@ import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlat
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundServiceDelegate;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccountServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualEventService;
@@ -177,6 +178,7 @@ import org.apache.fineract.portfolio.savings.service.GSIMReadPlatformService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.jdbc.core.JdbcTemplate;
@Configuration
@@ -344,7 +346,8 @@ public class LoanAccountConfiguration {
LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper,
LoanTransactionProcessingService loanTransactionProcessingService, LoanBalanceService loanBalanceService,
LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository,
LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository) {
LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository,
@Lazy InterestRefundServiceDelegate interestRefundServiceDelegate) {
return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository,
loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService,
fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService,
@@ -352,7 +355,7 @@ public class LoanAccountConfiguration {
loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator,
delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService,
loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService,
loanCapitalizedIncomeBalanceRepository, loanBuyDownFeeBalanceRepository);
loanCapitalizedIncomeBalanceRepository, loanBuyDownFeeBalanceRepository, interestRefundServiceDelegate);
}
@Bean