FINERACT-2080: Extract calculations from loan entity

This commit is contained in:
Oleksii Novikov
2025-05-22 13:58:44 +02:00
committed by Adam Saghy
parent 18a55874ba
commit 47d5330802
53 changed files with 1403 additions and 1128 deletions
@@ -104,11 +104,13 @@ import org.apache.fineract.portfolio.loanaccount.service.InterestRefundServiceDe
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
import org.apache.fineract.portfolio.loanaccount.service.LoanRefundService;
import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
@@ -147,7 +149,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
private final InterestRefundServiceDelegate interestRefundServiceDelegate;
private final LoanTransactionValidator loanTransactionValidator;
private final LoanForeclosureValidator loanForeclosureValidator;
@@ -161,6 +162,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
private final LoanTransactionProcessingService loanTransactionProcessingService;
private final LoanBalanceService loanBalanceService;
private final LoanTransactionService loanTransactionService;
@Transactional
@Override
@@ -767,7 +770,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
existingTransactionIds.addAll(loan.findExistingTransactionIds());
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
final ScheduleGeneratorDTO scheduleGeneratorDTO = null;
final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate);
final LoanRepaymentScheduleInstallment foreCloseDetail = loanBalanceService.fetchLoanForeclosureDetail(loan, foreClosureDate);
loanAccrualsProcessingService.processAccrualsOnLoanForeClosure(loan, foreClosureDate, newTransactions);
@@ -875,7 +878,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
LoanTransaction refundTransaction = LoanTransaction.refund(loan, loanTransactionType, transactionAmount, paymentDetail,
transactionDate, txnExternalId);
final boolean isTransactionChronologicallyLatest = loan.isChronologicallyLatestRepaymentOrWaiver(refundTransaction);
final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan,
refundTransaction);
boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
.map(LoanSupportedInterestRefundTypes::getTransactionType)
@@ -1002,7 +1006,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final List<LoanRepaymentScheduleInstallment> newInstallments = new ArrayList<>(loan.getRepaymentScheduleInstallments());
final MonetaryCurrency currency = loan.getCurrency();
Money totalPrincipal = Money.zero(currency);
final Money[] balances = loan.retrieveIncomeForOverlappingPeriod(transactionDate);
final Money[] balances = loanBalanceService.retrieveIncomeForOverlappingPeriod(loan, transactionDate);
boolean isInterestComponent = true;
for (final LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
if (!DateUtils.isAfter(transactionDate, installment.getDueDate())) {
@@ -1571,7 +1571,8 @@ public class LoanScheduleAssembler {
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
if (loanScheduleModelPeriod instanceof LoanScheduleModelDisbursementPeriod) {
for (final LoanCharge loanCharge : nonCompoundingCharges) {
loanCharge.populateDerivedFields(totalInterest, loanCharge.amountOrPercentage(), null, BigDecimal.ZERO);
loanChargeService.populateDerivedFields(loanCharge, totalInterest, loanCharge.amountOrPercentage(), null,
BigDecimal.ZERO);
loanScheduleModelPeriod.addLoanCharges(loanCharge.getAmountOutstanding(), BigDecimal.ZERO);
}
}
@@ -55,6 +55,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanTransactionAssembler loanTransactionAssembler;
private final LoanAccountService loanAccountService;
private final LoanBalanceService loanBalanceService;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
@@ -93,7 +94,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
return;
}
LocalDate closureDate = loan.isOverPaid() ? loan.getOverpaidOnDate() : loan.getClosedOnDate();
LocalDate closureDate = loanBalanceService.isOverPaid(loan) ? loan.getOverpaidOnDate() : loan.getClosedOnDate();
// Reverse accrual activities posted after the closure date
loan.getLoanTransactions(t -> t.isAccrualActivity() && !t.isReversed() && t.getDateOf().isAfter(closureDate))
@@ -113,6 +113,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
private final TransactionTemplate transactionTemplate;
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
private final LoanChargeService loanChargeService;
private final LoanBalanceService loanBalanceService;
/**
* method adds accrual for batch job "Add Periodic Accrual Transactions" and add accruals api for Loan
@@ -307,7 +308,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
// TODO implement progressive accrual case
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()
&& (loan.getAccruedTill() == null || !DateUtils.isEqual(foreClosureDate, loan.getAccruedTill()))) {
final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate);
final LoanRepaymentScheduleInstallment foreCloseDetail = loanBalanceService.fetchLoanForeclosureDetail(loan, foreClosureDate);
MonetaryCurrency currency = loan.getCurrency();
reverseTransactionsAfter(retrieveListOfAccrualTransactions(loan), foreClosureDate, false);
@@ -494,7 +495,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
Money interest = null;
final boolean isPastPeriod = isAfterPeriod(tillDate, installment);
final boolean isInPeriod = isInPeriod(tillDate, installment, false);
if (isPastPeriod || loan.isClosed() || loan.isOverPaid()) {
if (isPastPeriod || loan.isClosed() || loanBalanceService.isOverPaid(loan)) {
interest = installment.getInterestCharged(currency).minus(installment.getCreditedInterest());
} else {
if (isInPeriod) { // first period first day is not accrued
@@ -61,6 +61,7 @@ public class LoanChargeAssembler {
private final LoanChargeRepository loanChargeRepository;
private final LoanProductRepository loanProductRepository;
private final ExternalIdFactory externalIdFactory;
private final LoanChargeService loanChargeService;
public Set<LoanCharge> fromParsedJson(final JsonElement element, List<LoanDisbursementDetails> disbursementDetails) {
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element);
@@ -210,7 +211,7 @@ public class LoanChargeAssembler {
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
if (!loanCharge.isTrancheDisbursementCharge() || disbursementChargeIds.contains(loanChargeId)) {
loanCharge.update(amount, dueDate, numberOfRepayments);
loanChargeService.update(loanCharge, amount, dueDate, numberOfRepayments);
loanCharges.add(loanCharge);
}
@@ -296,7 +297,7 @@ public class LoanChargeAssembler {
if (percentage == null) {
percentage = chargeDefinition.getAmount();
}
loanCharge = LoanCharge.calculatePerInstallmentChargeAmount(loan,
loanCharge = loanChargeService.calculatePerInstallmentChargeAmount(loan,
ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation()), percentage);
}
@@ -314,7 +315,7 @@ public class LoanChargeAssembler {
}
ExternalId externalId = externalIdFactory.createFromCommand(command, "externalId");
return new LoanCharge(loan, chargeDefinition, amountPercentageAppliedTo, amount, chargeTime, chargeCalculation, dueDate,
return loanChargeService.create(loan, chargeDefinition, amountPercentageAppliedTo, amount, chargeTime, chargeCalculation, dueDate,
chargePaymentMode, null, loanCharge, externalId);
}
@@ -324,7 +325,7 @@ public class LoanChargeAssembler {
public LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final ExternalId externalId) {
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode,
numberOfRepayments, BigDecimal.ZERO, externalId);
return loanChargeService.create(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate,
chargePaymentMode, numberOfRepayments, BigDecimal.ZERO, externalId);
}
}
@@ -288,9 +288,9 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
.equals(loan.transactionProcessingStrategy())) {
// [For Adv payment allocation strategy] check if charge due date is earlier than last transaction
// date, if yes trigger reprocess else no reprocessing
LoanTransaction lastPaymentTransaction = loan.getLastTransactionForReprocessing();
if (lastPaymentTransaction != null
&& DateUtils.isAfter(loanCharge.getEffectiveDueDate(), lastPaymentTransaction.getTransactionDate())) {
final Optional<LocalDate> lastPaymentTransactionDate = loanTransactionRepository.findLastTransactionDateForReprocessing(loan);
if (lastPaymentTransactionDate.isPresent()
&& DateUtils.isAfter(loanCharge.getEffectiveDueDate(), lastPaymentTransactionDate.get())) {
reprocessRequired = false;
}
}
@@ -475,7 +475,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final Map<String, Object> changes = new LinkedHashMap<>(3);
if (loan.getActiveCharges().contains(loanCharge)) {
final BigDecimal amount = loanChargeService.calculateAmountPercentageAppliedTo(loan, loanCharge);
final Map<String, Object> loanChargeChanges = loanCharge.update(command, amount);
final Map<String, Object> loanChargeChanges = loanChargeService.update(command, amount, loanCharge);
changes.putAll(loanChargeChanges);
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
}
@@ -244,8 +244,8 @@ public class LoanDisbursementService {
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
final LoanCharge loanCharge = new LoanCharge(loan, chargeDefinition, principal, null, null, null, expectedDisbursementDate,
null, null, BigDecimal.ZERO, externalId);
final LoanCharge loanCharge = loanChargeService.create(loan, chargeDefinition, principal, null, null, null,
expectedDisbursementDate, null, null, BigDecimal.ZERO, externalId);
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
disbursementDetails);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
@@ -179,6 +179,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
private final LoanForeclosureValidator loanForeclosureValidator;
private final LoanTransactionMapper loanTransactionMapper;
private final LoanTransactionProcessingService loadTransactionProcessingService;
private final LoanBalanceService loanBalanceService;
@Override
public LoanAccountData retrieveOne(final Long loanId) {
@@ -2088,7 +2089,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
final LocalDate earliestUnpaidInstallmentDate = DateUtils.getBusinessLocalDate();
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchLoanForeclosureDetail(transactionDate);
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanBalanceService.fetchLoanForeclosureDetail(loan,
transactionDate);
BigDecimal unrecognizedIncomePortion = null;
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
@@ -2236,7 +2238,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
return loanRepositoryWrapper.existsByLoanId(loanId);
}
private LoanTransaction deriveDefaultInterestWaiverTransaction(Loan loan) {
private LoanTransaction deriveDefaultInterestWaiverTransaction(final Loan loan) {
final Money totalInterestOutstanding = loan.getTotalInterestOutstandingOnLoan();
Money possibleInterestToWaive = totalInterestOutstanding.copy();
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
@@ -2260,4 +2262,5 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
return LoanTransaction.waiver(loan.getOffice(), loan, possibleInterestToWaive, transactionDate, possibleInterestToWaive,
possibleInterestToWaive.zero(), ExternalId.empty());
}
}
@@ -61,6 +61,8 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
private final LoanTermVariationsMapper loanMapper;
private final InterestScheduleModelRepositoryWrapper modelRepository;
private final LoanTransactionService loanTransactionService;
private final LoanBalanceService loanBalanceService;
@Override
public boolean canProcessLatestTransactionOnly(Loan loan, LoanTransaction loanTransaction,
@@ -86,7 +88,7 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
new ChangedTransactionDetail(), savedModel.orElse(null), getTotalRefundInterestAmount(loan));
progressiveContext.getAlreadyProcessedTransactions().addAll(loan.retrieveListOfTransactionsForReprocessing());
progressiveContext.getAlreadyProcessedTransactions().addAll(loanTransactionService.retrieveListOfTransactionsForReprocessing(loan));
progressiveContext.setChargedOff(loan.isChargedOff());
ChangedTransactionDetail result = advancedProcessor.processLatestTransaction(loanTransaction, progressiveContext);
if (savedModel.isPresent() && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
@@ -158,7 +160,9 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
public Optional<ChangedTransactionDetail> processPostDisbursementTransactions(Loan loan) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
loan.getTransactionProcessingStrategyCode());
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionService
.retrieveListOfTransactionsForReprocessing(loan);
final List<LoanTransaction> copyTransactions = new ArrayList<>();
if (allNonContraTransactionsPostDisbursement.isEmpty()) {
@@ -173,7 +177,7 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
loan.getDisbursementDate(), copyTransactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges());
loan.updateLoanSummaryDerivedFields();
loanBalanceService.updateLoanSummaryDerivedFields(loan);
return Optional.of(changedTransactionDetail);
}
@@ -287,6 +287,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
private final LoanMapper loanMapper;
private final LoanTransactionProcessingService loanTransactionProcessingService;
private final LoanBalanceService loanBalanceService;
private final LoanTransactionService loanTransactionService;
@Transactional
@Override
@@ -572,7 +574,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_DISBURSED,
actualDisbursementDate1);
loanDisbursementService.handleDisbursementTransaction(loan, actualDisbursementDate1, paymentDetail1);
loan.updateLoanSummaryDerivedFields();
loanBalanceService.updateLoanSummaryDerivedFields(loan);
final Money interestApplied = Money.of(loan.getCurrency(), loan.getSummary().getTotalInterestCharged());
/*
@@ -591,11 +593,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
if (loan.getLoanProduct().isMultiDisburseLoan() || loan.isProgressiveSchedule()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionService
.retrieveListOfTransactionsForReprocessing(loan);
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
reprocessLoanTransactionsService.reprocessTransactions(loan);
}
loan.updateLoanSummaryDerivedFields();
loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, loan);
@@ -972,8 +975,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
newInterestPaymentWaiverTransaction.updateLoan(loan);
final boolean isTransactionChronologicallyLatest = loan
.isChronologicallyLatestRepaymentOrWaiver(newInterestPaymentWaiverTransaction);
final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan,
newInterestPaymentWaiverTransaction);
if (newInterestPaymentWaiverTransaction.isNotZero()) {
loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
@@ -1412,7 +1415,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Money unrecognizedIncome = transactionAmountAsMoney.zero();
Money interestComponent = transactionAmountAsMoney;
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
Money receivableInterest = loan.getReceivableInterest(transactionDate);
Money receivableInterest = loanBalanceService.getReceivableInterest(loan, transactionDate);
if (transactionAmountAsMoney.isGreaterThan(receivableInterest)) {
interestComponent = receivableInterest;
unrecognizedIncome = transactionAmountAsMoney.minus(receivableInterest);
@@ -2024,119 +2027,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
public void updateLoanRepaymentScheduleDates(final Loan loan, final LocalDate meetingStartDate, final String recuringRule,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean isSkipRepaymentonfirstdayofmonth, final Integer numberofDays) {
// first repayment's from date is same as disbursement date.
LocalDate tmpFromDate = loan.getDisbursementDate();
final PeriodFrequencyType repaymentPeriodFrequencyType = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType();
final Integer loanRepaymentInterval = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
LocalDate newRepaymentDate;
LocalDate latestRepaymentDate = null;
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
// FIXME: AA this won't update repayment dates before current date.
if (DateUtils.isAfter(oldDueDate, meetingStartDate) && DateUtils.isDateInTheFuture(oldDueDate)) {
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate,
loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays);
final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType,
loanRepaymentInterval, tmpFromDate);
if (DateUtils.isAfter(newRepaymentDate, maxDateLimitForNewRepayment)) {
newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, meetingStartDate, tmpFromDate,
loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays);
}
if (isHolidayEnabled) {
newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
}
if (DateUtils.isBefore(latestRepaymentDate, newRepaymentDate)) {
latestRepaymentDate = newRepaymentDate;
}
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
// reset from date to get actual daysInPeriod
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
tmpFromDate = newRepaymentDate;// update with new repayment date
} else {
tmpFromDate = oldDueDate;
}
}
if (latestRepaymentDate != null) {
loan.setExpectedMaturityDate(latestRepaymentDate);
}
}
private LocalDate getMaxDateLimitForNewRepayment(final PeriodFrequencyType periodFrequencyType, final Integer loanRepaymentInterval,
final LocalDate startDate) {
LocalDate dueRepaymentPeriodDate = startDate;
final int repaidEvery = 2 * loanRepaymentInterval;
switch (periodFrequencyType) {
case DAYS -> dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
case WEEKS -> dueRepaymentPeriodDate = startDate.plusWeeks(repaidEvery);
case MONTHS -> dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
case YEARS -> dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
case INVALID, WHOLE_TERM -> {
}
}
return dueRepaymentPeriodDate.minusDays(1);// get 2n-1 range date from startDate
}
private void updateLoanRepaymentScheduleDates(final Loan loan, final String recurringRule, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final LocalDate presentMeetingDate, final LocalDate newMeetingDate,
final boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
// first repayment's from date is same as disbursement date.
// meetingStartDate is used as seedDate Capture the seedDate from user and use the seedDate as meetingStart date
LocalDate tmpFromDate = loan.getDisbursementDate();
final PeriodFrequencyType repaymentPeriodFrequencyType = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType();
final Integer loanRepaymentInterval = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
LocalDate newRepaymentDate;
boolean isFirstTime = true;
LocalDate latestRepaymentDate = null;
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
if (!DateUtils.isBefore(oldDueDate, presentMeetingDate)) {
if (isFirstTime) {
isFirstTime = false;
newRepaymentDate = newMeetingDate;
} else {
// tmpFromDate.plusDays(1) is done to make sure
// getNewRepaymentMeetingDate method returns next meeting
// date and not the same as tmpFromDate
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recurringRule, tmpFromDate, tmpFromDate.plusDays(1),
loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays);
}
if (isHolidayEnabled) {
newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
}
if (DateUtils.isBefore(latestRepaymentDate, newRepaymentDate)) {
latestRepaymentDate = newRepaymentDate;
}
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
// reset from date to get actual daysInPeriod
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
tmpFromDate = newRepaymentDate;// update with new repayment date
} else {
tmpFromDate = oldDueDate;
}
}
if (latestRepaymentDate != null) {
loan.setExpectedMaturityDate(latestRepaymentDate);
}
}
private void removeLoanCycle(final Loan loan) {
final List<Loan> loansToUpdate;
if (loan.isGroupLoan()) {
@@ -2924,7 +2814,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
}
final List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsForReprocessing();
final List<LoanTransaction> loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
loanTransactions.add(chargeOffTransaction);
reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions);
loan.addLoanTransaction(chargeOffTransaction);
@@ -3117,9 +3007,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.addLoanTransaction(chargebackTransaction);
if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) {
final List<LoanTransaction> transactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
loanTransactionProcessingService.reprocessLoanTransactions(loan.getTransactionProcessingStrategyCode(),
loan.getDisbursementDate(), loan.retrieveListOfTransactionsForReprocessing(), loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
loan.getDisbursementDate(), transactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges());
} else {
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargebackTransaction,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
@@ -3215,7 +3106,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.adjustNetDisbursalAmount(loan.getApprovedPrincipal());
actualChanges.put(ACTUAL_DISBURSEMENT_DATE, "");
loan.updateLoanSummaryDerivedFields();
loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
return actualChanges;
@@ -3246,7 +3137,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
loan.updateLoanSummaryDerivedFields();
loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
private void reverseExistingTransactions(final Loan loan) {
@@ -3319,7 +3210,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
loan.updateLoanSummaryDerivedFields();
loanBalanceService.updateLoanSummaryDerivedFields(loan);
return Optional.of(loanTransaction);
}
@@ -3383,7 +3274,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
changes.put("externalId", externalId);
loanTransaction = LoanTransaction.writeoff(loan, loan.getOffice(), closureDate, externalId);
final boolean isLastTransaction = loan.isChronologicallyLatestTransaction(loanTransaction, loan.getLoanTransactions());
final boolean isLastTransaction = loanTransactionRepository.isChronologicallyLatest(loanTransaction.getTransactionDate(),
loan);
if (!isLastTransaction) {
final String errorMessage = "The closing date of the loan must be on or after latest transaction date.";
throw new InvalidLoanStateTransitionException("close.loan", "must.occur.on.or.after.latest.transaction.date",
@@ -3395,7 +3287,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
loan.updateLoanSummaryDerivedFields();
loanBalanceService.updateLoanSummaryDerivedFields(loan);
} else if (totalOutstanding.isGreaterThanZero()) {
final String errorMessage = "A loan with money outstanding cannot be closed";
throw new InvalidLoanStateTransitionException("close", "loan.has.money.outstanding", errorMessage,
@@ -3403,8 +3295,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
if (loan.isOverPaid()) {
final Money totalLoanOverpayment = loan.calculateTotalOverpayment();
if (loanBalanceService.isOverPaid(loan)) {
final Money totalLoanOverpayment = loanBalanceService.calculateTotalOverpayment(loan);
if (totalLoanOverpayment.isGreaterThanZero() && loan.getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) {
// TODO - KW - technically should set somewhere that this loan
// has 'overpaid' amount
@@ -3474,7 +3366,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
final LoanDisbursementDetails disbursementDetail = loan.getDisbursementDetails(lastTransactionDate,
lastDisbursalTransaction.getAmount());
loan.updateLoanToLastDisbursalState(disbursementDetail);
loanBalanceService.updateLoanToLastDisbursalState(loan, disbursementDetail);
loan.getLoanTermVariations()
.removeIf(loanTermVariations -> (loanTermVariations.getTermType().isDueDateVariation()
&& DateUtils.isAfter(loanTermVariations.fetchDateValue(), lastTransactionDate))
@@ -3579,4 +3471,118 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
return !statusEnum.hasStateOf(actualLoanStatus) || isMultiTrancheDisburse;
}
private void updateLoanRepaymentScheduleDates(final Loan loan, final LocalDate meetingStartDate, final String recuringRule,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean isSkipRepaymentonfirstdayofmonth, final Integer numberofDays) {
// first repayment's from date is same as disbursement date.
LocalDate tmpFromDate = loan.getDisbursementDate();
final PeriodFrequencyType repaymentPeriodFrequencyType = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType();
final Integer loanRepaymentInterval = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
LocalDate newRepaymentDate;
LocalDate latestRepaymentDate = null;
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
// FIXME: AA this won't update repayment dates before current date.
if (DateUtils.isAfter(oldDueDate, meetingStartDate) && DateUtils.isDateInTheFuture(oldDueDate)) {
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate,
loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays);
final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType,
loanRepaymentInterval, tmpFromDate);
if (DateUtils.isAfter(newRepaymentDate, maxDateLimitForNewRepayment)) {
newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, meetingStartDate, tmpFromDate,
loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays);
}
if (isHolidayEnabled) {
newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
}
if (DateUtils.isBefore(latestRepaymentDate, newRepaymentDate)) {
latestRepaymentDate = newRepaymentDate;
}
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
// reset from date to get actual daysInPeriod
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
tmpFromDate = newRepaymentDate;// update with new repayment date
} else {
tmpFromDate = oldDueDate;
}
}
if (latestRepaymentDate != null) {
loan.setExpectedMaturityDate(latestRepaymentDate);
}
}
private LocalDate getMaxDateLimitForNewRepayment(final PeriodFrequencyType periodFrequencyType, final Integer loanRepaymentInterval,
final LocalDate startDate) {
LocalDate dueRepaymentPeriodDate = startDate;
final int repaidEvery = 2 * loanRepaymentInterval;
switch (periodFrequencyType) {
case DAYS -> dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
case WEEKS -> dueRepaymentPeriodDate = startDate.plusWeeks(repaidEvery);
case MONTHS -> dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
case YEARS -> dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
case INVALID, WHOLE_TERM -> {
}
}
return dueRepaymentPeriodDate.minusDays(1);// get 2n-1 range date from startDate
}
private void updateLoanRepaymentScheduleDates(final Loan loan, final String recurringRule, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final LocalDate presentMeetingDate, final LocalDate newMeetingDate,
final boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
// first repayment's from date is same as disbursement date.
// meetingStartDate is used as seedDate Capture the seedDate from user and use the seedDate as meetingStart date
LocalDate tmpFromDate = loan.getDisbursementDate();
final PeriodFrequencyType repaymentPeriodFrequencyType = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType();
final Integer loanRepaymentInterval = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
LocalDate newRepaymentDate;
boolean isFirstTime = true;
LocalDate latestRepaymentDate = null;
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
if (!DateUtils.isBefore(oldDueDate, presentMeetingDate)) {
if (isFirstTime) {
isFirstTime = false;
newRepaymentDate = newMeetingDate;
} else {
// tmpFromDate.plusDays(1) is done to make sure
// getNewRepaymentMeetingDate method returns next meeting
// date and not the same as tmpFromDate
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recurringRule, tmpFromDate, tmpFromDate.plusDays(1),
loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays);
}
if (isHolidayEnabled) {
newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
}
if (DateUtils.isBefore(latestRepaymentDate, newRepaymentDate)) {
latestRepaymentDate = newRepaymentDate;
}
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
// reset from date to get actual daysInPeriod
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
tmpFromDate = newRepaymentDate;// update with new repayment date
} else {
tmpFromDate = oldDueDate;
}
}
if (latestRepaymentDate != null) {
loan.setExpectedMaturityDate(latestRepaymentDate);
}
}
}
@@ -53,6 +53,7 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
private final EMICalculator emiCalculator;
private final LoanRepositoryWrapper loanRepository;
private final LoanTransactionService loanTransactionService;
private final InterestScheduleModelRepositoryWrapper modelRepository;
@Override
@@ -80,8 +81,8 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
}
private ProgressiveLoanInterestScheduleModel calculateModel(Loan loan, LocalDate businessDate) {
List<LoanTransaction> transactionsToReprocess = loan.retrieveListOfTransactionsForReprocessing().stream()
.filter(t -> !t.isAccrualActivity()).toList();
final List<LoanTransaction> transactionsToReprocess = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan)
.stream().filter(t -> !t.isAccrualActivity()).toList();
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess, loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
@@ -22,6 +22,8 @@ import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -35,6 +37,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
@@ -49,15 +53,27 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
private final LoanTransactionProcessingService loanTransactionProcessingService;
private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper;
private final LoanBalanceService loanBalanceService;
private final LoanTransactionRepository loanTransactionRepository;
@Override
public void reprocessTransactions(final Loan loan) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(loan);
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
allNonContraTransactionsPostDisbursement);
handleChangedDetail(changedTransactionDetail);
}
private List<LoanTransaction> retrieveListOfTransactionsForReprocessing(final Loan loan) {
return loan.getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate())
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
}
private Predicate<LoanTransaction> loanTransactionForReprocessingPredicate() {
return transaction -> transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge()
|| transaction.isAccrualActivity() || transaction.isReAmortize() || !transaction.isNonMonetaryTransaction());
}
@Override
public void reprocessParticularTransactions(final Loan loan, final List<LoanTransaction> loanTransactions) {
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, loanTransactions);
@@ -66,8 +82,8 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
@Override
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
loan.retrieveListOfTransactionsForReprocessing());
final List<LoanTransaction> transactions = retrieveListOfTransactionsForReprocessing(loan);
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, transactions);
handleChangedDetail(changedTransactionDetail);
}
@@ -101,7 +117,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
return;
}
loan.getLoanCharges().remove(loanCharge);
loan.updateLoanSummaryDerivedFields();
loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
private void removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(final Loan loan, final LoanCharge loanCharge) {
@@ -147,7 +163,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
.map(TransactionChangeData::getNewTransaction).peek(transaction -> transaction.updateLoan(loan)).toList();
loan.getLoanTransactions().addAll(newTransactions);
loan.updateLoanSummaryDerivedFields();
loanBalanceService.updateLoanSummaryDerivedFields(loan);
handleChangedDetail(changedTransactionDetail);
}
@@ -185,7 +201,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
.map(TransactionChangeData::getNewTransaction).toList();
loan.getLoanTransactions().addAll(newTransactions);
loan.updateLoanSummaryDerivedFields();
loanBalanceService.updateLoanSummaryDerivedFields(loan);
return changedTransactionDetail;
}
}
@@ -60,6 +60,7 @@ import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidat
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
@@ -91,6 +92,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
private final LoanChargeValidator loanChargeValidator;
private final LoanJournalEntryPoster journalEntryPoster;
private final LoanBalanceService loanBalanceService;
@Override
public CommandProcessingResult adjustLoanTransaction(Loan loan, LoanTransaction transactionToAdjust, LoanAdjustmentParameter parameter,
@@ -112,7 +114,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
Money unrecognizedIncome = transactionAmountAsMoney.zero();
Money interestComponent = transactionAmountAsMoney;
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
Money receivableInterest = loan.getReceivableInterest(transactionDate);
Money receivableInterest = loanBalanceService.getReceivableInterest(loan, transactionDate);
if (transactionAmountAsMoney.isGreaterThan(receivableInterest)) {
interestComponent = receivableInterest;
unrecognizedIncome = transactionAmountAsMoney.minus(receivableInterest);
@@ -33,6 +33,9 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanInterestRefundServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
@@ -48,63 +51,77 @@ public class LoanAccountAutoStarter {
@Bean
@Conditional(CreocoreLoanRepaymentScheduleTransactionProcessorCondition.class)
public CreocoreLoanRepaymentScheduleTransactionProcessor creocoreLoanRepaymentScheduleTransactionProcessor(
ExternalIdFactory externalIdFactory) {
return new CreocoreLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) {
return new CreocoreLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Bean
@Conditional(EarlyRepaymentLoanRepaymentScheduleTransactionProcessorCondition.class)
public EarlyPaymentLoanRepaymentScheduleTransactionProcessor earlyPaymentLoanRepaymentScheduleTransactionProcessor(
ExternalIdFactory externalIdFactory) {
return new EarlyPaymentLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) {
return new EarlyPaymentLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Bean
@Conditional(MifosStandardLoanRepaymentScheduleTransactionProcessorCondition.class)
public FineractStyleLoanRepaymentScheduleTransactionProcessor fineractStyleLoanRepaymentScheduleTransactionProcessor(
ExternalIdFactory externalIdFactory) {
return new FineractStyleLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) {
return new FineractStyleLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Bean
@Conditional(HeavensFamilyLoanRepaymentScheduleTransactionProcessorCondition.class)
public HeavensFamilyLoanRepaymentScheduleTransactionProcessor heavensFamilyLoanRepaymentScheduleTransactionProcessor(
ExternalIdFactory externalIdFactory) {
return new HeavensFamilyLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) {
return new HeavensFamilyLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Bean
@Conditional(InterestPrincipalPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.class)
public InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor interestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
ExternalIdFactory externalIdFactory) {
return new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) {
return new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
loanBalanceService);
}
@Bean
@Conditional(PrincipalInterestPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.class)
public PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor principalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
ExternalIdFactory externalIdFactory) {
return new PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) {
return new PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
loanBalanceService);
}
@Bean
@Conditional(RBIIndiaLoanRepaymentScheduleTransactionProcessorCondition.class)
public RBILoanRepaymentScheduleTransactionProcessor rbiLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
return new RBILoanRepaymentScheduleTransactionProcessor(externalIdFactory);
public RBILoanRepaymentScheduleTransactionProcessor rbiLoanRepaymentScheduleTransactionProcessor(
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) {
return new RBILoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Bean
@Conditional(DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorCondition.class)
public DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor duePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(
ExternalIdFactory externalIdFactory) {
return new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) {
return new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
loanBalanceService);
}
@Bean
@Conditional(DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorCondition.class)
public DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor duePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(
ExternalIdFactory externalIdFactory) {
return new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) {
return new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
loanBalanceService);
}
@Bean
@@ -117,11 +134,13 @@ public class LoanAccountAutoStarter {
@Bean
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator,
LoanRepositoryWrapper loanRepositoryWrapper,
@Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService, ExternalIdFactory externalIdFactory,
LoanScheduleComponent loanSchedule) {
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator,
final LoanRepositoryWrapper loanRepositoryWrapper,
final @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService,
final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule,
final LoanTransactionService loanTransactionService, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) {
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService,
externalIdFactory, loanSchedule);
externalIdFactory, loanSchedule, loanTransactionService, loanChargeValidator, loanBalanceService);
}
}
@@ -118,6 +118,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService
import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssemblerImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService;
import org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationEventService;
import org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationProcessingService;
@@ -143,6 +144,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanStatusChangePlatfor
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionRelationReadService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformServiceJpaRepositoryImpl;
@@ -268,11 +270,11 @@ public class LoanAccountConfiguration {
@Bean
@ConditionalOnMissingBean(LoanChargeAssembler.class)
public LoanChargeAssembler loanChargeAssembler(
FromJsonHelper fromApiJsonHelper, ChargeRepositoryWrapper chargeRepository, LoanChargeRepository loanChargeRepository,
LoanProductRepository loanProductRepository, ExternalIdFactory externalIdFactory) {
return new LoanChargeAssembler(fromApiJsonHelper, chargeRepository, loanChargeRepository, loanProductRepository, externalIdFactory);
public LoanChargeAssembler loanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository,
final LoanChargeRepository loanChargeRepository, final LoanProductRepository loanProductRepository,
final ExternalIdFactory externalIdFactory, final LoanChargeService loanChargeService) {
return new LoanChargeAssembler(fromApiJsonHelper, chargeRepository, loanChargeRepository, loanProductRepository, externalIdFactory,
loanChargeService);
}
@Bean
@@ -331,14 +333,14 @@ public class LoanAccountConfiguration {
DelinquencyReadPlatformService delinquencyReadPlatformService, LoanTransactionRepository loanTransactionRepository,
LoanChargePaidByReadService loanChargePaidByReadService, LoanTransactionRelationReadService loanTransactionRelationReadService,
LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper,
LoanTransactionProcessingService loanTransactionProcessingService) {
LoanTransactionProcessingService loanTransactionProcessingService, LoanBalanceService loanBalanceService) {
return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository,
loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService,
fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService,
staffReadPlatformService, paginationHelper, paymentTypeReadPlatformService, floatingRatesReadPlatformService,
loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator,
delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService,
loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService);
loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService);
}
@Bean
@@ -402,7 +404,8 @@ public class LoanAccountConfiguration {
ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanAccountService loanAccountService,
LoanJournalEntryPoster journalEntryPoster, LoanAdjustmentService loanAdjustmentService,
LoanAccountingBridgeMapper loanAccountingBridgeMapper, LoanMapper loanMapper,
LoanTransactionProcessingService loanTransactionProcessingService) {
LoanTransactionProcessingService loanTransactionProcessingService, final LoanBalanceService loanBalanceService,
LoanTransactionService loanTransactionService) {
return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer,
loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository,
loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository,
@@ -417,7 +420,7 @@ public class LoanAccountConfiguration {
loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator,
loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService, reprocessLoanTransactionsService,
loanAccountService, journalEntryPoster, loanAdjustmentService, loanAccountingBridgeMapper, loanMapper,
loanTransactionProcessingService);
loanTransactionProcessingService, loanBalanceService, loanTransactionService);
}
@Bean
@@ -434,10 +437,12 @@ public class LoanAccountConfiguration {
LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanScheduleService loanScheduleService,
LoanRefundService loanRefundService, LoanRefundValidator loanRefundValidator,
ReprocessLoanTransactionsService reprocessLoanTransactionsService,
LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine) {
LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine,
LoanBalanceService loanBalanceService, LoanTransactionService loanTransactionService) {
return new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService,
loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator,
reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine);
reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService,
loanTransactionService);
}
@Bean
@@ -457,9 +462,10 @@ public class LoanAccountConfiguration {
@Bean
@ConditionalOnMissingBean(LoanChargeService.class)
public LoanChargeService loanChargeService(LoanChargeValidator loanChargeValidator,
LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine) {
return new LoanChargeService(loanChargeValidator, loanTransactionProcessingService, loanLifecycleStateMachine);
public LoanChargeService loanChargeService(final LoanChargeValidator loanChargeValidator,
final LoanTransactionProcessingService loanTransactionProcessingService,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanBalanceService loanBalanceService) {
return new LoanChargeService(loanChargeValidator, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService);
}
@Bean
@@ -87,12 +87,17 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -122,6 +127,9 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
@Mock
private AvroDateTimeMapper mapper;
private final LoanChargeService loanChargeService = new LoanChargeService(mock(LoanChargeValidator.class),
mock(LoanTransactionProcessingService.class), mock(LoanLifecycleStateMachine.class), mock(LoanBalanceService.class));
private MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
private static final String CUSTOM_DATA_PREFIX = "test_data_loan_delinquency_range_business_event";
@@ -275,7 +283,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
.thenReturn(installmentDelinquencyTags);
when(loanForProcessing.getLoanCharges()).thenAnswer(a -> repaymentScheduleInstallments.get(0).getInstallmentCharges().stream()
.map(c -> c.getLoanCharge()).collect(Collectors.toList()));
.map(LoanInstallmentCharge::getLoanCharge).collect(Collectors.toSet()));
// when
LoanAccountDelinquencyRangeDataV1 data = (LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event);
@@ -431,8 +439,9 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
}
private LoanCharge buildLoanCharge(Loan loan, BigDecimal amount, Charge charge) {
LoanCharge loanCharge = new LoanCharge(loan, charge, amount, amount, ChargeTimeType.SPECIFIED_DUE_DATE, ChargeCalculationType.FLAT,
LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1, new BigDecimal(100), ExternalId.generate());
LoanCharge loanCharge = loanChargeService.create(loan, charge, amount, amount, ChargeTimeType.SPECIFIED_DUE_DATE,
ChargeCalculationType.FLAT, LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1, new BigDecimal(100),
ExternalId.generate());
ReflectionTestUtils.setField(loanCharge, "id", 1L);
return loanCharge;
}
@@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -48,6 +49,9 @@ class DefaultLoanLifecycleStateMachineTest {
@Mock
private BusinessEventNotifierService businessEventNotifierService;
@Mock
private LoanBalanceService loanBalanceService;
private DefaultLoanLifecycleStateMachine underTest;
private MockedStatic<MoneyHelper> moneyHelperStatic;
@@ -58,7 +62,7 @@ class DefaultLoanLifecycleStateMachineTest {
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
moneyHelperStatic.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
moneyHelperStatic.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
underTest = new DefaultLoanLifecycleStateMachine(businessEventNotifierService);
underTest = new DefaultLoanLifecycleStateMachine(businessEventNotifierService, loanBalanceService);
}
@AfterEach
@@ -41,6 +41,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.Loa
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.rate.domain.Rate;
@@ -61,7 +63,7 @@ public class LoanBuilder {
private Staff loanOfficer;
private CodeValue loanPurpose;
private LoanRepaymentScheduleTransactionProcessor transactionProcessor = new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
mock(ExternalIdFactory.class));
mock(ExternalIdFactory.class), mock(LoanChargeValidator.class), mock(LoanBalanceService.class));
private LoanProductRelatedDetail loanRepaymentScheduleDetail;
private LoanStatus loanStatus = LoanStatus.SUBMITTED_AND_PENDING_APPROVAL;
private LoanSubStatus loanSubStatus;
@@ -265,8 +265,23 @@ public class LoanTest {
* @return the {@link LoanCharge}
*/
private LoanCharge buildLoanCharge() {
return new LoanCharge(mock(Loan.class), mock(Charge.class), new BigDecimal(100), new BigDecimal(100),
ChargeTimeType.TRANCHE_DISBURSEMENT, ChargeCalculationType.FLAT, LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1,
new BigDecimal(100), ExternalId.generate());
final LoanCharge loanCharge = new LoanCharge();
loanCharge.setLoan(mock(Loan.class));
loanCharge.setCharge(mock(Charge.class));
loanCharge.setAmount(new BigDecimal(100));
loanCharge.setAmountOutstanding(new BigDecimal(100));
loanCharge.setChargeTime(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue());
loanCharge.setChargeCalculation(ChargeCalculationType.FLAT.getValue());
loanCharge.setDueDate(LocalDate.of(2022, 6, 27));
loanCharge.setChargePaymentMode(ChargePaymentMode.REGULAR.getValue());
loanCharge.setPercentage(null);
loanCharge.setAmountPercentageAppliedTo(null);
loanCharge.setAmountPaid(null);
loanCharge.setAmountWaived(null);
loanCharge.setAmountWrittenOff(null);
loanCharge.setExternalId(ExternalId.generate());
return loanCharge;
}
}
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.refEq;
import static org.mockito.Mockito.mock;
import java.math.BigDecimal;
import java.math.MathContext;
@@ -34,6 +35,7 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.ActionContext;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
@@ -44,6 +46,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
@@ -99,7 +103,8 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
@BeforeEach
public void setUp() {
underTest = new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(null);
underTest = new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(mock(ExternalIdFactory.class),
mock(LoanChargeValidator.class), mock(LoanBalanceService.class));
Mockito.reset(charges, transactionMappings);
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.refEq;
import static org.mockito.Mockito.mock;
import java.math.BigDecimal;
import java.math.MathContext;
@@ -34,6 +35,7 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.ActionContext;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
@@ -44,6 +46,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
@@ -100,7 +104,8 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
@BeforeEach
public void setUp() {
underTest = new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(null);
underTest = new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(mock(ExternalIdFactory.class),
mock(LoanChargeValidator.class), mock(LoanBalanceService.class));
Mockito.reset(charges, transactionMappings);
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
@@ -111,13 +111,20 @@ public class LoanDownPaymentHandlerServiceImplTest {
@Mock
private LoanLifecycleStateMachine loanLifecycleStateMachine;
@Mock
private LoanBalanceService loanBalanceService;
@Mock
private LoanTransactionService loanTransactionService;
private LoanDownPaymentHandlerServiceImpl underTest;
@BeforeEach
public void setUp() {
underTest = new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService,
loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator,
reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine);
reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService,
loanTransactionService);
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
tempConfigServiceMock.when(TemporaryConfigurationServiceContainer::isExternalIdAutoGenerationEnabled).thenReturn(true);