in fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java [331:445]
private void addAccruals(@NotNull final Loan loan, @NotNull LocalDate tillDate, final boolean periodic, final boolean isFinal,
final boolean addJournal, final boolean chargeOnDueDate) {
if ((!isFinal && !loan.isOpen()) || loan.isNpa() || loan.isChargedOff()
|| !loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
return;
}
final LoanInterestRecalculationDetails recalculationDetails = loan.getLoanInterestRecalculationDetails();
if (recalculationDetails != null && recalculationDetails.isCompoundingToBePostedAsTransaction()) {
return;
}
final List<LoanTransaction> existingAccruals = retrieveListOfAccrualTransactions(loan);
final LocalDate lastDueDate = loan.getLastLoanRepaymentScheduleInstallment().getDueDate();
reverseTransactionsAfter(existingAccruals, lastDueDate, addJournal);
ensureAccrualTransactionMappings(loan, existingAccruals, chargeOnDueDate);
if (DateUtils.isAfter(tillDate, lastDueDate)) {
tillDate = lastDueDate;
}
final boolean progressiveAccrual = isProgressiveAccrual(loan);
final LocalDate accruedTill = loan.getAccruedTill();
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
final LocalDate accrualDate = isFinal
? (progressiveAccrual ? (DateUtils.isBefore(lastDueDate, businessDate) ? lastDueDate : businessDate)
: getFinalAccrualTransactionDate(loan))
: tillDate;
if (progressiveAccrual && accruedTill != null && !DateUtils.isAfter(tillDate, accruedTill)) {
if (isFinal) {
reverseTransactionsAfter(existingAccruals, accrualDate, addJournal);
} else if (existingAccruals.stream().anyMatch(t -> !t.isReversed() && !DateUtils.isBefore(t.getDateOf(), accrualDate))) {
return;
}
}
final AccrualPeriodsData accrualPeriods = calculateAccrualAmounts(loan, tillDate, periodic, isFinal, chargeOnDueDate);
final boolean mergeTransactions = isFinal || progressiveAccrual;
final MonetaryCurrency currency = loan.getLoanProductRelatedDetail().getCurrency();
List<LoanTransaction> accrualTransactions = new ArrayList<>();
Money totalInterestPortion = null;
LoanTransaction mergeAccrualTransaction = null;
LoanTransaction mergeAdjustTransaction = null;
for (AccrualPeriodData period : accrualPeriods.getPeriods()) {
final Money interestAccruable = MathUtil.nullToZero(period.getInterestAccruable(), currency);
final Money interestPortion = MathUtil.minus(interestAccruable, period.getInterestAccrued());
final Money feeAccruable = MathUtil.nullToZero(period.getFeeAccruable(), currency);
final Money feePortion = MathUtil.minus(feeAccruable, period.getFeeAccrued());
final Money penaltyAccruable = MathUtil.nullToZero(period.getPenaltyAccruable(), currency);
final Money penaltyPortion = MathUtil.minus(penaltyAccruable, period.getPenaltyAccrued());
if (MathUtil.isEmpty(interestPortion) && MathUtil.isEmpty(feePortion) && MathUtil.isEmpty(penaltyPortion)) {
continue;
}
if (mergeTransactions) {
totalInterestPortion = MathUtil.plus(totalInterestPortion, interestPortion);
if (progressiveAccrual) {
final Money feeAdjustmentPortion = MathUtil.negate(feePortion);
final Money penaltyAdjustmentPortion = MathUtil.negate(penaltyPortion);
mergeAdjustTransaction = createOrMergeAccrualTransaction(loan, mergeAdjustTransaction, accrualDate, period,
accrualTransactions, null, feeAdjustmentPortion, penaltyAdjustmentPortion, true);
}
mergeAccrualTransaction = createOrMergeAccrualTransaction(loan, mergeAccrualTransaction, accrualDate, period,
accrualTransactions, null, feePortion, penaltyPortion, false);
} else {
final LocalDate dueDate = period.getDueDate();
if (!isFinal && DateUtils.isAfter(dueDate, tillDate) && DateUtils.isBefore(tillDate, accruedTill)) {
continue;
}
final LocalDate periodAccrualDate = DateUtils.isBefore(dueDate, accrualDate) ? dueDate : accrualDate;
final LoanTransaction accrualTransaction = addAccrualTransaction(loan, periodAccrualDate, period, interestPortion,
feePortion, penaltyPortion, false);
if (accrualTransaction != null) {
accrualTransactions.add(accrualTransaction);
}
}
final LoanRepaymentScheduleInstallment installment = loan.fetchRepaymentScheduleInstallment(period.getInstallmentNumber());
installment.updateAccrualPortion(interestAccruable, feeAccruable, penaltyAccruable);
}
if (mergeTransactions && !MathUtil.isEmpty(totalInterestPortion)) {
if (progressiveAccrual) {
final Money interestAdjustmentPortion = MathUtil.negate(totalInterestPortion);
createOrMergeAccrualTransaction(loan, mergeAdjustTransaction, accrualDate, null, accrualTransactions,
interestAdjustmentPortion, null, null, true);
}
createOrMergeAccrualTransaction(loan, mergeAccrualTransaction, accrualDate, null, accrualTransactions, totalInterestPortion,
null, null, false);
}
if (accrualTransactions.isEmpty()) {
return;
}
if (!isFinal || progressiveAccrual) {
loan.setAccruedTill(isFinal ? accrualDate : tillDate);
}
accrualTransactions = loanTransactionRepository.saveAll(accrualTransactions);
loanTransactionRepository.flush();
if (addJournal) {
final List<AccountingBridgeLoanTransactionDTO> newTransactionDTOs = new ArrayList<>();
for (LoanTransaction accrualTransaction : accrualTransactions) {
final LoanTransactionBusinessEvent businessEvent = accrualTransaction.isAccrual()
? new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction)
: new LoanAccrualAdjustmentTransactionBusinessEvent(accrualTransaction);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
final AccountingBridgeLoanTransactionDTO transactionDTO = loanAccountingBridgeMapper
.mapToLoanTransactionData(accrualTransaction, currency.getCode());
newTransactionDTOs.add(transactionDTO);
}
final AccountingBridgeDataDTO accountingBridgeData = new AccountingBridgeDataDTO(loan.getId(), loan.getLoanProduct().getId(),
loan.getOfficeId(), loan.getCurrencyCode(), loan.getSummary().getTotalInterestCharged(),
loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct(),
loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(), false,
false, false, null, newTransactionDTOs);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
}