diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java index be86204ab2e..d11a556d8cf 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java @@ -357,17 +357,29 @@ public static String format(LocalDateTime dateTime, String format, Locale locale * * @param targetDate * the date to be checked - * @param startDate + * @param fromDate * the start date of the range - * @param endDate + * @param toDate * the end date of the range * @return true if targetDate is within range or equal to start/end dates, otherwise false */ - public static boolean isDateWithinRange(LocalDate targetDate, LocalDate startDate, LocalDate endDate) { - if (targetDate == null || startDate == null || endDate == null) { + public static boolean isDateWithinRange(LocalDate targetDate, LocalDate fromDate, LocalDate toDate) { + if (targetDate == null || fromDate == null || toDate == null) { throw new IllegalArgumentException("Dates must not be null"); } - return !targetDate.isBefore(startDate) && !targetDate.isAfter(endDate); + return isDateInRangeInclusive(targetDate, fromDate, toDate); + } + + public static boolean isDateInRangeInclusive(LocalDate targetDate, LocalDate fromDate, LocalDate toDate) { + return fromDate != null && !DateUtils.isBefore(targetDate, fromDate) && !DateUtils.isAfter(targetDate, toDate); + } + + public static boolean isDateInRangeExclusive(LocalDate targetDate, LocalDate fromDate, LocalDate toDate) { + return fromDate != null && DateUtils.isAfter(targetDate, fromDate) && DateUtils.isBefore(targetDate, toDate); + } + + public static boolean isDateInRangeFromExclusiveToInclusive(LocalDate targetDate, LocalDate fromDate, LocalDate toDate) { + return fromDate != null && DateUtils.isAfter(targetDate, fromDate) && !DateUtils.isAfter(targetDate, toDate); } @NotNull @@ -393,14 +405,4 @@ private static DateTimeFormatter getDateTimeFormatter(String format, Locale loca } return formatter; } - - public static boolean occursOnDayFromExclusiveAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive, - final LocalDate target) { - return DateUtils.isAfter(target, fromNotInclusive) && !DateUtils.isAfter(target, upToAndInclusive); - } - - public static boolean occursOnDayFromAndUpToAndIncluding(final LocalDate fromAndInclusive, final LocalDate upToAndInclusive, - final LocalDate target) { - return target != null && !DateUtils.isBefore(target, fromAndInclusive) && !DateUtils.isAfter(target, upToAndInclusive); - } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 82f58a30834..8f162c34eec 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -714,8 +714,7 @@ public void addLoanCharge(final LoanCharge loanCharge) { public ChangedTransactionDetail reprocessTransactions() { ChangedTransactionDetail changedTransactionDetail; - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); @@ -786,20 +785,15 @@ private void handleChargePaidTransaction(final LoanCharge charge, final LoanTran addLoanTransaction(chargesPayment); loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT, this); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List chargePaymentInstallments = new ArrayList<>(); List installments = getRepaymentScheduleInstallments(); int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper .fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments); for (final LoanRepaymentScheduleInstallment installment : installments) { - boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber) - ? charge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate()) - : charge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate()); - if (installmentNumber == null && isFirstNormalInstallment) { - chargePaymentInstallments.add(installment); - break; - } else if (installment.getInstallmentNumber().equals(installmentNumber)) { + boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber); + if (installment.getInstallmentNumber().equals(installmentNumber) || (installmentNumber == null + && charge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment))) { chargePaymentInstallments.add(installment); break; } @@ -868,8 +862,7 @@ public void removeLoanCharge(final LoanCharge loanCharge) { removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loanCharge); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { /* * TODO Vishwas Currently we do not allow removing a loan charge after a loan is approved (hence there is no @@ -931,8 +924,7 @@ public Map updateLoanCharge(final LoanCharge loanCharge, final J updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); if (!loanCharge.isDueAtDisbursement()) { /* * TODO Vishwas Currently we do not allow waiving updating loan charge after a loan is approved (hence there @@ -1103,8 +1095,7 @@ public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLi } // Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule // to be reprocessed. - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { /* * TODO Vishwas Currently we do not allow waiving fully paid loan charge and waiving partially paid loan @@ -1689,7 +1680,7 @@ public Map undoApproval(final LoanLifecycleStateMachine loanLife validateAccountStatus(LoanEvent.LOAN_APPROVAL_UNDO); final Map actualChanges = new LinkedHashMap<>(); - final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus); + final LoanStatus currentStatus = getStatus(); final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_APPROVAL_UNDO, this); if (!statusEnum.hasStateOf(currentStatus)) { loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVAL_UNDO, this); @@ -1738,7 +1729,7 @@ public boolean canDisburse() { final LoanStatus statusEnum = this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSED, this); boolean isMultiTrancheDisburse = false; - LoanStatus actualLoanStatus = LoanStatus.fromInt(this.loanStatus); + LoanStatus actualLoanStatus = getStatus(); if ((actualLoanStatus.isActive() || actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid()) && isAllTranchesNotDisbursed()) { isMultiTrancheDisburse = true; @@ -2114,7 +2105,7 @@ public Map undoDisbursal(final ScheduleGeneratorDTO scheduleGene validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO); final Map actualChanges = new LinkedHashMap<>(); - final LoanStatus currentStatus = LoanStatus.fromInt(this.loanStatus); + final LoanStatus currentStatus = getStatus(); final LoanStatus statusEnum = this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSAL_UNDO, this); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO, getDisbursementDate()); existingTransactionIds.addAll(findExistingTransactionIds()); @@ -2389,11 +2380,9 @@ private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(fi } } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); - final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallment( - loanTransaction.getTransactionDate()); + final LoanRepaymentScheduleInstallment currentInstallment = getRepaymentScheduleInstallment(loanTransaction.getTransactionDate()); boolean reprocess = isForeclosure() || !isTransactionChronologicallyLatest || adjustedTransaction != null || !DateUtils.isEqualBusinessDate(loanTransaction.getTransactionDate()) || currentInstallment == null @@ -2467,12 +2456,6 @@ private LocalDate extractTransactionDate(LoanTransaction loanTransaction) { return loanTransactionDate; } - public LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallment(LocalDate dueDate) { - return getRepaymentScheduleInstallments().stream() // - .filter(installment -> dueDate.equals(installment.getDueDate())).findFirst() // - .orElse(null); - } - public List retrieveListOfTransactionsForReprocessing() { return getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate()).sorted(LoanTransactionComparator.INSTANCE) .collect(Collectors.toList()); @@ -2711,8 +2694,7 @@ public ChangedTransactionDetail undoWrittenOff(LoanLifecycleStateMachine loanLif final LoanTransaction writeOffTransaction = findWriteOffTransaction(); writeOffTransaction.reverse(); loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, this); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); @@ -2787,8 +2769,7 @@ public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, fin final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) { - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, loanRepaymentScheduleTransactionProcessor); @@ -2801,7 +2782,7 @@ public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, fin final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.WRITE_OFF_OUTSTANDING, this); LoanTransaction loanTransaction = null; - if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { + if (!statusEnum.hasStateOf(getStatus())) { loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, this); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); @@ -2919,8 +2900,7 @@ public ChangedTransactionDetail close(final JsonCommand command, final LoanLifec final String errorMessage = "The date on which a loan is closed cannot be in the future."; throw new InvalidLoanStateTransitionException("close", "cannot.be.a.future.date", errorMessage, closureDate); } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, loanRepaymentScheduleTransactionProcessor); @@ -2931,7 +2911,7 @@ public ChangedTransactionDetail close(final JsonCommand command, final LoanLifec this.closedOnDate = closureDate; final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.REPAID_IN_FULL, this); - if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { + if (!statusEnum.hasStateOf(getStatus())) { loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, this); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); } @@ -2963,7 +2943,7 @@ public ChangedTransactionDetail close(final JsonCommand command, final LoanLifec // has 'overpaid' amount this.closedOnDate = closureDate; final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.REPAID_IN_FULL, this); - if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { + if (!statusEnum.hasStateOf(getStatus())) { loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, this); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); } @@ -2990,7 +2970,7 @@ public void closeAsMarkedForReschedule(final JsonCommand command, final LoanLife this.closedOnDate = rescheduledOn; final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_RESCHEDULE, this); - if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { + if (!statusEnum.hasStateOf(getStatus())) { loanLifecycleStateMachine.transition(LoanEvent.LOAN_RESCHEDULE, this); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); } @@ -3078,7 +3058,7 @@ public boolean isOpen() { } public boolean isAllTranchesNotDisbursed() { - LoanStatus actualLoanStatus = LoanStatus.fromInt(this.loanStatus); + LoanStatus actualLoanStatus = getStatus(); boolean isInRightStatus = actualLoanStatus.isActive() || actualLoanStatus.isApproved() || actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid(); return this.loanProduct.isMultiDisburseLoan() && isInRightStatus && isDisbursementAllowed(); @@ -4154,8 +4134,7 @@ public ChangedTransactionDetail updateDisbursementDateAndAmountForTranche(final regenerateRepaymentSchedule(scheduleGeneratorDTO); } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), @@ -4200,21 +4179,6 @@ public BigDecimal retriveLastEmiAmount() { return emiAmount; } - public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) { - LoanRepaymentScheduleInstallment installment = null; - if (installmentNumber == null) { - return installment; - } - List installments = getRepaymentScheduleInstallments(); - for (final LoanRepaymentScheduleInstallment scheduleInstallment : installments) { - if (scheduleInstallment.getInstallmentNumber().equals(installmentNumber)) { - installment = scheduleInstallment; - break; - } - } - return installment; - } - public Money getTotalOverpaidAsMoney() { return Money.of(this.repaymentScheduleDetail().getCurrency(), this.totalOverpaid); } @@ -4279,8 +4243,7 @@ public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRec } public ChangedTransactionDetail processTransactions() { - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), @@ -4332,8 +4295,7 @@ public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleG } public void processPostDisbursementTransactions() { - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); final List copyTransactions = new ArrayList<>(); if (!allNonContraTransactionsPostDisbursement.isEmpty()) { @@ -4360,8 +4322,7 @@ private LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO gener final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, this, generatorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor, generatorDTO.getRecalculateFrom()); @@ -4378,8 +4339,7 @@ public OutstandingAmountsDTO fetchPrepaymentDetail(final ScheduleGeneratorDTO sc final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory() .create(loanApplicationTerms.getLoanScheduleType(), interestMethod); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); outstandingAmounts = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this, scheduleGeneratorDTO.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor); } else { @@ -4544,39 +4504,36 @@ public void addLoanRepaymentScheduleInstallment(final LoanRepaymentScheduleInsta this.repaymentScheduleInstallments.add(installment); } + /** + * @param date + * @return a schedule installment is related to the provided date + **/ + public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(LocalDate date) { + // TODO first installment should be fromInclusive + return getRepaymentScheduleInstallment(e -> DateUtils.isDateInRangeFromExclusiveToInclusive(date, e.getFromDate(), e.getDueDate())); + } + + public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) { + return getRepaymentScheduleInstallment(e -> e.getInstallmentNumber().equals(installmentNumber)); + } + /** * @param dueDate * the due date of the installment * @return a schedule installment with similar due date to the one provided **/ public LoanRepaymentScheduleInstallment getRepaymentScheduleInstallment(LocalDate dueDate) { - LoanRepaymentScheduleInstallment installment = null; - - if (dueDate != null) { - List installments = getRepaymentScheduleInstallments(); - for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : installments) { - if (DateUtils.isEqual(dueDate, repaymentScheduleInstallment.getDueDate())) { - installment = repaymentScheduleInstallment; - break; - } - } - } - return installment; + return getRepaymentScheduleInstallment(e -> DateUtils.isEqual(dueDate, e.getDueDate())); } /** - * @param date - * @return a schedule installment is related to the provided date + * @param predicate + * filter of the installments + * @return the first installment matching the filter **/ - public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(LocalDate date) { - if (date == null) { - return null; - } - return getRepaymentScheduleInstallments()// - .stream()// - .filter(installment -> date.isAfter(installment.getFromDate()) && !date.isAfter(installment.getDueDate()))// - .findAny()// - .orElse(null);// + public LoanRepaymentScheduleInstallment getRepaymentScheduleInstallment( + @NotNull Predicate predicate) { + return getRepaymentScheduleInstallments().stream().filter(predicate).findFirst().orElse(null); } /** @@ -4867,8 +4824,7 @@ private ChangedTransactionDetail handleRefundTransaction(final LoanTransaction l } } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); // If it's a refund if (adjustedTransaction == null) { @@ -4898,8 +4854,7 @@ public void handleChargebackTransaction(final LoanTransaction chargebackTransact throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.chargeback.transaction", errorMessage); } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategyCode); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); addLoanTransaction(chargebackTransaction); loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargebackTransaction, new TransactionCtx(getCurrency(), @@ -5183,9 +5138,7 @@ private Money[] fetchInterestFeeAndPenaltyTillDate(final LocalDate paymentDate, interestAccountedForCurrentPeriod = installment.getInterestWaived(getCurrency()).plus(installment.getInterestPaid(getCurrency())); for (LoanCharge loanCharge : this.charges) { if (loanCharge.isActive() && !loanCharge.isDueAtDisbursement()) { - boolean isDue = isFirstNormalInstallment - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), paymentDate) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), paymentDate); + boolean isDue = loanCharge.isDueInPeriod(installment.getFromDate(), paymentDate, isFirstNormalInstallment); if (isDue) { if (loanCharge.isPenaltyCharge()) { penaltyForCurrentPeriod = penaltyForCurrentPeriod.plus(loanCharge.getAmount(getCurrency())); @@ -5235,8 +5188,7 @@ public Money[] retriveIncomeForOverlappingPeriod(final LocalDate paymentDate) { balances[1] = fee; balances[2] = penalty; break; - } else if (DateUtils.isAfter(paymentDate, installment.getFromDate()) - && DateUtils.isBefore(paymentDate, installment.getDueDate())) { + } else if (DateUtils.isDateInRangeExclusive(paymentDate, installment.getFromDate(), installment.getDueDate())) { balances = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment, isFirstNormalInstallment); break; } @@ -5524,4 +5476,8 @@ public void deductFromNetDisbursalAmount(final BigDecimal subtrahend) { public void setIsTopup(boolean topup) { isTopup = topup; } + + private LoanRepaymentScheduleTransactionProcessor getTransactionProcessor() { + return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode); + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 249be53208f..9e3dcfdb1b7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -616,14 +616,8 @@ public boolean hasLoanIdentifiedBy(final Long loanId) { return this.loan.hasIdentifyOf(loanId); } - public boolean isDueForCollectionFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) { - final LocalDate dueDate = getDueLocalDate(); - return DateUtils.occursOnDayFromExclusiveAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, dueDate); - } - - public boolean isDueForCollectionFromIncludingAndUpToAndIncluding(final LocalDate fromAndInclusive, final LocalDate upToAndInclusive) { - final LocalDate dueDate = getDueLocalDate(); - return DateUtils.occursOnDayFromAndUpToAndIncluding(fromAndInclusive, upToAndInclusive, dueDate); + public boolean isDueInPeriod(final LocalDate fromDate, final LocalDate toDate, boolean isFirstPeriod) { + return LoanRepaymentScheduleProcessingWrapper.isInPeriod(getDueLocalDate(), fromDate, toDate, isFirstPeriod); } public boolean isFeeCharge() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java index fef7d0f6635..643c114f1b4 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java @@ -22,6 +22,7 @@ import java.time.LocalDate; import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -87,7 +88,7 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L Money cumulative = Money.zero(monetaryCurrency); for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { cumulative = cumulative.plus(getInstallmentFee(monetaryCurrency, period, loanCharge)); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { @@ -133,7 +134,7 @@ private Money cumulativeChargesWaivedWithin(final LocalDate periodStart, final L for (final LoanCharge loanCharge : loanCharges) { if (predicate.test(loanCharge)) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { @@ -156,7 +157,7 @@ private Money cumulativeChargesWrittenOffWithin(final LocalDate periodStart, fin for (final LoanCharge loanCharge : loanCharges) { if (chargePredicate.test(loanCharge)) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { @@ -175,11 +176,6 @@ private Predicate feeCharge() { return loanCharge -> loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement(); } - private boolean loanChargeIsDue(LocalDate periodStart, LocalDate periodEnd, boolean isFirstPeriod, LoanCharge loanCharge) { - return isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); - } - private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable, boolean isFirstPeriod) { @@ -188,7 +184,7 @@ private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, fin for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { cumulative = cumulative.plus(getInstallmentFee(currency, period, loanCharge)); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { @@ -242,17 +238,24 @@ public static int fetchFirstNormalInstallmentNumber(List !repaymentPeriod.isDownPayment()).findFirst().orElseThrow().getInstallmentNumber(); } - public static boolean isInPeriod(LocalDate transactionDate, LoanRepaymentScheduleInstallment targetInstallment, + public static boolean isInPeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment targetInstallment, List installments) { int firstPeriod = fetchFirstNormalInstallmentNumber(installments); - return isInPeriod(transactionDate, targetInstallment, targetInstallment.getInstallmentNumber().equals(firstPeriod)); + return isInPeriod(targetDate, targetInstallment, targetInstallment.getInstallmentNumber().equals(firstPeriod)); + } + + public static boolean isInPeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment installment, boolean isFirstPeriod) { + return isInPeriod(targetDate, installment.getFromDate(), installment.getDueDate(), isFirstPeriod); } - private static boolean isInPeriod(LocalDate transactionDate, LoanRepaymentScheduleInstallment targetInstallment, - boolean isFirstPeriod) { - LocalDate fromDate = targetInstallment.getFromDate(); - LocalDate dueDate = targetInstallment.getDueDate(); - return isFirstPeriod ? DateUtils.occursOnDayFromAndUpToAndIncluding(fromDate, dueDate, transactionDate) - : DateUtils.occursOnDayFromExclusiveAndUpToAndIncluding(fromDate, dueDate, transactionDate); + public static boolean isInPeriod(LocalDate targetDate, LocalDate fromDate, LocalDate toDate, boolean isFirstPeriod) { + return isFirstPeriod ? DateUtils.isDateInRangeInclusive(targetDate, fromDate, toDate) + : DateUtils.isDateInRangeFromExclusiveToInclusive(targetDate, fromDate, toDate); + } + + public static Optional findInPeriod(LocalDate targetDate, + List installments) { + int firstNumber = fetchFirstNormalInstallmentNumber(installments); + return installments.stream().filter(e -> isInPeriod(targetDate, e, e.getInstallmentNumber().equals(firstNumber))).findFirst(); } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java index 83c0bc07f31..7bee2ce78c9 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapper.java @@ -27,6 +27,7 @@ import org.apache.fineract.infrastructure.core.service.MathUtil; 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.charge.domain.ChargeCalculationType; /** @@ -35,187 +36,142 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper { public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate, - final List repaymentPeriods, LoanCharge loanCharge) { + final List installments, LoanCharge loanCharge) { Loan loan = loanCharge.getLoan(); Money zero = Money.zero(currency); Money totalInterest = zero; Money totalPrincipal = zero; - for (final LoanRepaymentScheduleInstallment installment : repaymentPeriods) { + for (final LoanRepaymentScheduleInstallment installment : installments) { totalInterest = totalInterest.plus(installment.getInterestCharged(currency)); totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency)); } LoanChargePaidBy accrualBy = null; if (!loan.isInterestBearing() && loanCharge.isSpecifiedDueDate()) { // TODO: why only if not interest bearing - LoanRepaymentScheduleInstallment addedPeriod = addChargeOnlyRepaymentInstallmentIfRequired(loanCharge, repaymentPeriods); - if (addedPeriod != null) { - addedPeriod.updateObligationsMet(currency, disbursementDate); - } + addChargeOnlyRepaymentInstallmentIfRequired(loanCharge, installments); accrualBy = loanCharge.getLoanChargePaidBySet().stream().filter(e -> e.getLoanTransaction().isAccrual()).findFirst() .orElse(null); } LocalDate startDate = disbursementDate; - int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(repaymentPeriods); - for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) { - if (period.isDownPayment()) { + int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments); + for (final LoanRepaymentScheduleInstallment installment : installments) { + if (installment.isDownPayment()) { continue; } - boolean installmentChargeApplicable = !period.isRecalculatedInterestComponent(); - boolean isFirstNonDownPaymentPeriod = period.getInstallmentNumber().equals(firstNormalInstallmentNumber); - LocalDate dueDate = period.getDueDate(); - final Money feeChargesDueForRepaymentPeriod = feeChargesDueWithin(startDate, dueDate, loanCharge, currency, period, - totalPrincipal, totalInterest, installmentChargeApplicable, isFirstNonDownPaymentPeriod); - final Money feeChargesWaivedForRepaymentPeriod = chargesWaivedWithin(startDate, dueDate, loanCharge, currency, - installmentChargeApplicable, isFirstNonDownPaymentPeriod, feeCharge()); - final Money feeChargesWrittenOffForRepaymentPeriod = loanChargesWrittenOffWithin(startDate, dueDate, loanCharge, currency, - installmentChargeApplicable, isFirstNonDownPaymentPeriod, feeCharge()); + boolean installmentChargeApplicable = !installment.isRecalculatedInterestComponent(); + boolean isFirstPeriod = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber); + Predicate feePredicate = e -> e.isFeeCharge() && !e.isDueAtDisbursement(); + LocalDate dueDate = installment.getDueDate(); + final Money feeChargesDue = calcChargeDue(startDate, dueDate, loanCharge, currency, installment, totalPrincipal, totalInterest, + installmentChargeApplicable, isFirstPeriod, feePredicate); + final Money feeChargesWaived = calcChargeWaived(startDate, dueDate, loanCharge, currency, installmentChargeApplicable, + isFirstPeriod, feePredicate); + final Money feeChargesWrittenOff = calcChargeWrittenOff(startDate, dueDate, loanCharge, currency, installmentChargeApplicable, + isFirstPeriod, feePredicate); Predicate penaltyPredicate = LoanCharge::isPenaltyCharge; - final Money penaltyChargesDueForRepaymentPeriod = penaltyChargesDueWithin(startDate, dueDate, loanCharge, currency, period, - totalPrincipal, totalInterest, installmentChargeApplicable, isFirstNonDownPaymentPeriod); - final Money penaltyChargesWaivedForRepaymentPeriod = chargesWaivedWithin(startDate, dueDate, loanCharge, currency, - installmentChargeApplicable, isFirstNonDownPaymentPeriod, penaltyPredicate); - final Money penaltyChargesWrittenOffForRepaymentPeriod = loanChargesWrittenOffWithin(startDate, dueDate, loanCharge, currency, - installmentChargeApplicable, isFirstNonDownPaymentPeriod, penaltyPredicate); + final Money penaltyChargesDue = calcChargeDue(startDate, dueDate, loanCharge, currency, installment, totalPrincipal, + totalInterest, installmentChargeApplicable, isFirstPeriod, penaltyPredicate); + final Money penaltyChargesWaived = calcChargeWaived(startDate, dueDate, loanCharge, currency, installmentChargeApplicable, + isFirstPeriod, penaltyPredicate); + final Money penaltyChargesWrittenOff = calcChargeWrittenOff(startDate, dueDate, loanCharge, currency, + installmentChargeApplicable, isFirstPeriod, penaltyPredicate); - period.addToChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod, - feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod, - penaltyChargesWrittenOffForRepaymentPeriod); + installment.addToChargePortion(feeChargesDue, feeChargesWaived, feeChargesWrittenOff, penaltyChargesDue, penaltyChargesWaived, + penaltyChargesWrittenOff); - if (accrualBy != null && period.isAdditional() - && loanChargeIsDue(startDate, dueDate, isFirstNonDownPaymentPeriod, loanCharge)) { + if (accrualBy != null && installment.isAdditional() && loanCharge.isDueInPeriod(startDate, dueDate, isFirstPeriod)) { Money amount = Money.of(currency, accrualBy.getAmount()); boolean isFee = loanCharge.isFeeCharge(); - period.updateAccrualPortion(period.getInterestAccrued(currency), - MathUtil.plus(period.getFeeAccrued(currency), (isFee ? amount : null)), - MathUtil.plus(period.getPenaltyAccrued(currency), (isFee ? null : amount))); - accrualBy.setInstallmentNumber(period.getInstallmentNumber()); + installment.updateAccrualPortion(installment.getInterestAccrued(currency), + MathUtil.plus(installment.getFeeAccrued(currency), (isFee ? amount : null)), + MathUtil.plus(installment.getPenaltyAccrued(currency), (isFee ? null : amount))); + accrualBy.setInstallmentNumber(installment.getInstallmentNumber()); } startDate = dueDate; } } - private Money feeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, - final MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, - final Money totalInterest, boolean isInstallmentChargeApplicable, boolean isFirstPeriod) { - - if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); - if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - return Money.of(monetaryCurrency, getInstallmentFee(monetaryCurrency, period, loanCharge)); - } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { - return Money.of(monetaryCurrency, loanCharge.chargeAmount()); - } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount()); - } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { - amount = amount.add(totalInterest.getAmount()); - } else { - // If charge type is specified due date and loan is - // multi disburment loan. - // Then we need to get as of this loan charge due date - // how much amount disbursed. - if (loanCharge.getLoan() != null && loanCharge.isSpecifiedDueDate() && loanCharge.getLoan().isMultiDisburmentLoan()) { - for (final LoanDisbursementDetails loanDisbursementDetails : loanCharge.getLoan().getDisbursementDetails()) { - if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) { - amount = amount.add(loanDisbursementDetails.principal()); - } - } - } else { - amount = amount.add(totalPrincipal.getAmount()); - } + @NotNull + private Money calcChargeDue(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, + final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, final Money totalInterest, + boolean isInstallmentChargeApplicable, boolean isFirstPeriod, Predicate predicate) { + Money zero = Money.zero(currency); + if (!predicate.test(loanCharge)) { + return zero; + } + if (loanCharge.isFeeCharge() && loanCharge.isDueAtDisbursement()) { + return zero; + } + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { + return Money.of(currency, getInstallmentFee(currency, period, loanCharge)); + } + if (!loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod)) { + return zero; + } + ChargeCalculationType calculationType = loanCharge.getChargeCalculation(); + if (loanCharge.isOverdueInstallmentCharge() && calculationType.isPercentageBased()) { + return Money.of(currency, loanCharge.chargeAmount()); + } + if (calculationType.isFlat()) { + return loanCharge.getAmount(currency); + } + BigDecimal baseAmount = BigDecimal.ZERO; + Loan loan = loanCharge.getLoan(); + if (loan != null && loanCharge.isFeeCharge() && !calculationType.hasInterest() && loanCharge.isSpecifiedDueDate() + && loan.isMultiDisburmentLoan()) { + // If charge type is specified due date and loan is multi disburment loan. + // Then we need to get as of this loan charge due date how much amount disbursed. + for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) { + if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) { + baseAmount = MathUtil.add(baseAmount, loanDisbursementDetails.principal()); } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - return Money.of(monetaryCurrency, loanChargeAmt); - } else if (isDue) { - return Money.of(monetaryCurrency, loanCharge.amount()); } + } else { + baseAmount = getBaseAmount(loanCharge, totalPrincipal.getAmount(), totalInterest.getAmount()); } - return Money.zero(monetaryCurrency); + return Money.of(currency, MathUtil.percentageOf(baseAmount, loanCharge.getPercentage(), MoneyHelper.getMathContext())); } - private Money chargesWaivedWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, + private Money calcChargeWaived(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, boolean isFirstPeriod, Predicate predicate) { - - if (predicate.test(loanCharge)) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); - if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); - if (loanChargePerInstallment != null) { - return loanChargePerInstallment.getAmountWaived(currency); - } - } else if (isDue) { - return loanCharge.getAmountWaived(currency); - } + Money zero = Money.zero(currency); + if (!predicate.test(loanCharge)) { + return zero; } - - return Money.zero(currency); + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { + LoanInstallmentCharge installmentCharge = loanCharge.getInstallmentLoanCharge(periodEnd); + return installmentCharge == null ? zero : installmentCharge.getAmountWaived(currency); + } + if (loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod)) { + return loanCharge.getAmountWaived(currency); + } + return zero; } - private Money loanChargesWrittenOffWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, + private Money calcChargeWrittenOff(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, final MonetaryCurrency currency, boolean isInstallmentChargeApplicable, boolean isFirstPeriod, - Predicate chargePredicate) { - if (chargePredicate.test(loanCharge)) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); - if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); - if (loanChargePerInstallment != null) { - return loanChargePerInstallment.getAmountWrittenOff(currency); - } - } else if (isDue) { - return loanCharge.getAmountWrittenOff(currency); - } + Predicate predicate) { + Money zero = Money.zero(currency); + if (!predicate.test(loanCharge)) { + return zero; } - return Money.zero(currency); - } - - private Predicate feeCharge() { - return loanCharge -> loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement(); - } - - private boolean loanChargeIsDue(LocalDate periodStart, LocalDate periodEnd, boolean isFirstPeriod, LoanCharge loanCharge) { - return isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); - } - - private Money penaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final LoanCharge loanCharge, - final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal, final Money totalInterest, - boolean isInstallmentChargeApplicable, boolean isFirstPeriod) { - - if (loanCharge.isPenaltyCharge()) { - boolean isDue = loanChargeIsDue(periodStart, periodEnd, isFirstPeriod, loanCharge); - if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { - return Money.of(currency, getInstallmentFee(currency, period, loanCharge)); - } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { - return Money.of(currency, loanCharge.chargeAmount()); - } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) { - BigDecimal amount = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount()); - } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { - amount = amount.add(totalInterest.getAmount()); - } else { - amount = amount.add(totalPrincipal.getAmount()); - } - BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - return Money.of(currency, loanChargeAmt); - } else if (isDue) { - return Money.of(currency, loanCharge.amount()); - } + if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { + LoanInstallmentCharge installmentCharge = loanCharge.getInstallmentLoanCharge(periodEnd); + return installmentCharge == null ? zero : installmentCharge.getAmountWrittenOff(currency); } - - return Money.zero(currency); + if (loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod)) { + return loanCharge.getAmountWrittenOff(currency); + } + return zero; } private BigDecimal getInstallmentFee(MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, LoanCharge loanCharge) { - if (loanCharge.getChargeCalculation().isPercentageBased()) { - BigDecimal amount = BigDecimal.ZERO; - amount = getBaseAmount(currency, period, loanCharge, amount); - return amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); - } else { + if (loanCharge.getChargeCalculation().isFlat()) { return loanCharge.amountOrPercentage(); } + return MathUtil.percentageOf(getBaseAmount(currency, period, loanCharge, null), loanCharge.getPercentage(), + MoneyHelper.getMathContext()); } @NotNull diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index 73d909b05bc..582362a8ee4 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -113,9 +113,7 @@ public ChangedTransactionDetail reprocessLoanTransactions(final LocalDate disbur for (final LoanRepaymentScheduleInstallment installment : installments) { boolean isFirstPeriod = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber); for (final LoanCharge loanCharge : transferCharges) { - boolean isDue = isFirstPeriod - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate()) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate()); + boolean isDue = loanCharge.isDueInPeriod(startDate, installment.getDueDate(), isFirstPeriod); if (isDue) { Money amountForProcess = loanCharge.getAmount(currency); if (amountForProcess.isGreaterThan(loanTransaction.getAmount(currency))) { @@ -227,11 +225,8 @@ protected void calculateAccrualActivity(LoanTransaction loanTransaction, Monetar final int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments); final LoanRepaymentScheduleInstallment currentInstallment = installments.stream() - .filter(installment -> installment.getInstallmentNumber().equals(firstNormalInstallmentNumber) - ? DateUtils.occursOnDayFromExclusiveAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate(), - loanTransaction.getTransactionDate()) - : DateUtils.occursOnDayFromAndUpToAndIncluding(installment.getFromDate(), installment.getDueDate(), - loanTransaction.getTransactionDate())) + .filter(installment -> LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(), installment, + installment.getInstallmentNumber().equals(firstNormalInstallmentNumber))) .findFirst().orElseThrow(); interestPortion = interestPortion.plus(currentInstallment.getInterestCharged(currency)); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java index b891ace31bf..dd0f0c3bbca 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java @@ -2076,8 +2076,7 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L for (final LoanCharge loanCharge : loanCharges) { if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { @@ -2139,8 +2138,7 @@ private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, fin for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 32f44df2595..17dd652d5dd 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -1112,9 +1112,8 @@ private void handleChargePayment(LoanTransaction loanTransaction, TransactionCtx int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper .fetchFirstNormalInstallmentNumber(transactionCtx.getInstallments()); for (final LoanRepaymentScheduleInstallment installment : transactionCtx.getInstallments()) { - boolean isDue = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber) - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate()) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate()); + boolean isDue = loanCharge.isDueInPeriod(startDate, installment.getDueDate(), + installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)); if (isDue) { Integer installmentNumber = installment.getInstallmentNumber(); Money paidAmount = loanCharge.updatePaidAmountBy(amountToBePaid, installmentNumber, zero); @@ -1574,12 +1573,11 @@ private LocalDate calculateNewPayDateInCaseOfInAdvancePayment(LoanTransaction lo } @NotNull - private Set getLoanChargesOfInstallment(Set charges, LoanRepaymentScheduleInstallment currentInstallment, - int firstNormalInstallmentNumber) { - return charges.stream().filter(loanCharge -> currentInstallment.getInstallmentNumber().equals(firstNormalInstallmentNumber) - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(currentInstallment.getFromDate(), - currentInstallment.getDueDate()) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(currentInstallment.getFromDate(), currentInstallment.getDueDate())) + private Set getLoanChargesOfInstallment(Set charges, LoanRepaymentScheduleInstallment installment, + int firstInstallmentNumber) { + boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstInstallmentNumber); + return charges.stream() + .filter(loanCharge -> loanCharge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment)) .collect(Collectors.toSet()); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java index 2de29d2c969..15f83a38301 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -40,6 +40,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; @@ -254,26 +255,23 @@ public LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicatio public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { + if (!(loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) { + throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor"); + } + List installments = loan.getRepaymentScheduleInstallments(); + LoanRepaymentScheduleInstallment actualInstallment = LoanRepaymentScheduleProcessingWrapper.findInPeriod(onDate, installments) + .orElseThrow(() -> new IllegalStateException("No installment found for transaction date: " + onDate)); LocalDate transactionDate = switch (loanApplicationTerms.getPreClosureInterestCalculationStrategy()) { case TILL_PRE_CLOSURE_DATE -> onDate; - case TILL_REST_FREQUENCY_DATE -> // find due date of current installment - installments.stream().filter(it -> it.getFromDate().isBefore(onDate) && it.getDueDate().isAfter(onDate)).findFirst() - .orElse(installments.get(0)).getDueDate(); + case TILL_REST_FREQUENCY_DATE -> actualInstallment.getDueDate(); // due date of current installment case NONE -> throw new IllegalStateException("Unexpected PreClosureInterestCalculationStrategy: NONE"); }; - if (!(loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) { - throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor"); - } ProgressiveLoanInterestScheduleModel model = processor.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), loan.retrieveListOfTransactionsForReprocessing(), currency, installments, loan.getActiveCharges()).getRight(); - LoanRepaymentScheduleInstallment actualInstallment = installments.stream() - .filter(it -> transactionDate.isAfter(it.getFromDate()) && !transactionDate.isAfter(it.getDueDate())).findFirst() - .orElse(installments.get(0)); - PayableDetails result = emiCalculator.getPayableDetails(model, actualInstallment.getDueDate(), transactionDate); // TODO: We should add all the past due outstanding amounts as well OutstandingAmountsDTO amounts = new OutstandingAmountsDTO(currency) // @@ -336,8 +334,7 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L private Money getCumulativeAmountOfCharge(LocalDate periodStart, LocalDate periodEnd, PrincipalInterest principalInterestForThisPeriod, Money principalDisbursed, Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, boolean isFirstPeriod, LoanCharge loanCharge, Money cumulative, MathContext mc) { - boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd); + boolean isDue = loanCharge.isDueInPeriod(periodStart, periodEnd, isFirstPeriod); if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc); } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) { diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java index ffef3502a36..29e2ddd1e06 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java @@ -142,7 +142,7 @@ public void chargePaymentTransactionTestWithExactAmount() { when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney); when(loanTransaction.getLoan()).thenReturn(loan); when(loan.getDisbursementDate()).thenReturn(disbursementDate); - when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true); + when(charge.isDueInPeriod(disbursementDate, installment.getDueDate(), true)).thenReturn(true); when(installment.getInstallmentNumber()).thenReturn(1); when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney); when(loanTransaction.isPenaltyPayment()).thenReturn(false); @@ -186,7 +186,7 @@ public void chargePaymentTransactionTestWithLessTransactionAmount() { when(charge.getAmountOutstanding(currency)).thenReturn(chargeAmountMoney); when(loanTransaction.getLoan()).thenReturn(loan); when(loan.getDisbursementDate()).thenReturn(disbursementDate); - when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true); + when(charge.isDueInPeriod(disbursementDate, installment.getDueDate(), true)).thenReturn(true); when(installment.getInstallmentNumber()).thenReturn(1); when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney); when(loanTransaction.isPenaltyPayment()).thenReturn(false); @@ -234,7 +234,7 @@ public void chargePaymentTransactionTestWithMoreTransactionAmount() { when(loanTransaction.getLoan().getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL); when(loan.getDisbursementDate()).thenReturn(disbursementDate); - when(charge.isDueForCollectionFromIncludingAndUpToAndIncluding(disbursementDate, installment.getDueDate())).thenReturn(true); + when(charge.isDueInPeriod(disbursementDate, installment.getDueDate(), true)).thenReturn(true); when(installment.getInstallmentNumber()).thenReturn(1); when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney); when(loanTransaction.isPenaltyPayment()).thenReturn(false); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java index ece6bcdd8bf..e278393166a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java @@ -972,9 +972,7 @@ private void generateLoanScheduleAccrualData(final LocalDate accruedTill, } for (final LoanCharge loanCharge : loanCharges) { - boolean isDue = isFirstNormalInstallment - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), chargesTillDate) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), chargesTillDate); + boolean isDue = loanCharge.isDueInPeriod(installment.getFromDate(), chargesTillDate, isFirstNormalInstallment); if (isDue) { if (loanCharge.isFeeCharge()) { dueDateFeeIncome = dueDateFeeIncome.add(loanCharge.amount()); @@ -1010,9 +1008,7 @@ private void createAccrualTransactionAndUpdateChargesPaidBy(Loan loan, LocalDate loan.addLoanTransaction(accrualTransaction); Set accrualCharges = accrualTransaction.getLoanChargesPaid(); for (LoanCharge loanCharge : loan.getActiveCharges()) { - boolean isDue = DateUtils.isEqual(fromDate, loan.getDisbursementDate()) - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(fromDate, foreClosureDate) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, foreClosureDate); + boolean isDue = loanCharge.isDueInPeriod(fromDate, foreClosureDate, DateUtils.isEqual(fromDate, loan.getDisbursementDate())); if (loanCharge.isActive() && !loanCharge.isPaid() && (isDue || loanCharge.isInstalmentFee())) { final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrualTransaction, loanCharge, loanCharge.getAmountOutstanding(currency).getAmount(), null); @@ -1177,9 +1173,7 @@ private void determineFeeDetails(Loan loan, LocalDate fromDate, LocalDate toDate List loanCharges = new ArrayList<>(); List loanInstallmentCharges = new ArrayList<>(); for (LoanCharge loanCharge : loan.getActiveCharges()) { - boolean isDue = DateUtils.isEqual(fromDate, loan.getDisbursementDate()) - ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(fromDate, toDate) - : loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, toDate); + boolean isDue = loanCharge.isDueInPeriod(fromDate, toDate, DateUtils.isEqual(fromDate, loan.getDisbursementDate())); if (isDue) { if (loanCharge.isPenaltyCharge() && !loanCharge.isInstalmentFee()) { penalties = penalties.add(loanCharge.amount()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 34ca03940e9..c9e0e54b44a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1109,7 +1109,7 @@ private ChangedTransactionDetail recalculateLoanWithInterestPaymentWaiverTxn(Loa .determineProcessor(loan.getTransactionProcessingStrategyCode()); final LoanRepaymentScheduleInstallment currentInstallment = loan - .fetchLoanRepaymentScheduleInstallment(newInterestPaymentWaiverTransaction.getTransactionDate()); + .getRepaymentScheduleInstallment(newInterestPaymentWaiverTransaction.getTransactionDate()); boolean reprocess = true;