in fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java [318:535]
public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand command, Boolean isAccountTransfer,
Boolean isWithoutAutoPayment) {
loanTransactionValidator.validateDisbursement(command, isAccountTransfer, loanId);
Loan loan = loanAssembler.assembleFrom(loanId);
if (loan.loanProduct().isDisallowExpectedDisbursements()) {
List<LoanDisbursementDetails> filteredList = loan.getDisbursementDetails().stream()
.filter(disbursementDetails -> disbursementDetails.actualDisbursementDate() == null).toList();
// Check whether a new LoanDisbursementDetails is required
if (filteredList.isEmpty()) {
// create artificial 'tranche/expected disbursal' as current disburse code expects it for
// multi-disbursal products
final LocalDate artificialExpectedDate = loan.getExpectedDisbursedOnLocalDate();
LoanDisbursementDetails disbursementDetail = new LoanDisbursementDetails(artificialExpectedDate, null,
loan.getDisbursedAmount(), null, false);
disbursementDetail.updateLoan(loan);
loan.getAllDisbursementDetails().add(disbursementDetail);
}
}
final LocalDate nextPossibleRepaymentDate = loan.getNextPossibleRepaymentDateForRescheduling();
final LocalDate rescheduledRepaymentDate = command.localDateValueOfParameterNamed("adjustRepaymentDate");
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
if (!loan.isMultiDisburmentLoan()) {
loan.setActualDisbursementDate(actualDisbursementDate);
}
// validate actual disbursement date against meeting date
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null);
businessEventNotifierService.notifyPreBusinessEvent(new LoanDisbursalBusinessEvent(loan));
List<Long> existingTransactionIds = new ArrayList<>();
List<Long> existingReversedTransactionIds = new ArrayList<>();
final AppUser currentUser = getAppUserIfPresent();
final Map<String, Object> changes = new LinkedHashMap<>();
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
if (paymentDetail != null && paymentDetail.getPaymentType() != null && paymentDetail.getPaymentType().getIsCashPayment()) {
BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
this.cashierTransactionDataValidator.validateOnLoanDisbursal(currentUser, loan.getCurrencyCode(), transactionAmount);
}
final boolean isPaymentTypeApplicableForDisbursementCharge = configurationDomainService
.isPaymentTypeApplicableForDisbursementCharge();
// Recalculate first repayment date based in actual disbursement date.
updateLoanCounters(loan, actualDisbursementDate);
Money amountBeforeAdjust = loan.getPrincipal();
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
if (canDisburse(loan)) {
// Get netDisbursalAmount from disbursal screen field.
final BigDecimal netDisbursalAmount = command
.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementNetDisbursalAmountParameterName);
if (netDisbursalAmount != null) {
loan.setNetDisbursalAmount(netDisbursalAmount);
}
Money disburseAmount = loanDisbursementService.adjustDisburseAmount(loan, command, actualDisbursementDate);
Money amountToDisburse = disburseAmount.copy();
boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincipal());
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
if (loan.isTopup() && loan.getClientId() != null) {
final BigDecimal loanOutstanding = loanApplicationValidator.validateTopupLoan(loan, actualDisbursementDate);
amountToDisburse = disburseAmount.minus(loanOutstanding);
disburseLoanToLoan(loan, command, loanOutstanding);
}
LoanTransaction disbursementTransaction = null;
if (isAccountTransfer) {
disburseLoanToSavings(loan, command, amountToDisburse, paymentDetail);
existingTransactionIds.addAll(loan.findExistingTransactionIds());
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
} else {
existingTransactionIds.addAll(loan.findExistingTransactionIds());
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
disbursementTransaction = LoanTransaction.disbursement(loan, amountToDisburse, paymentDetail, actualDisbursementDate,
txnExternalId, loan.getTotalOverpaidAsMoney());
disbursementTransaction.updateLoan(loan);
loan.addLoanTransaction(disbursementTransaction);
loanTransactionRepository.saveAndFlush(disbursementTransaction);
}
if (loan.getRepaymentScheduleInstallments().isEmpty()) {
/*
* If no schedule, generate one (applicable to non-tranche multi-disbursal loans)
*/
recalculateSchedule = true;
}
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate,
rescheduledRepaymentDate);
boolean downPaymentEnabled = loan.repaymentScheduleDetail().isEnableDownPayment();
if (loan.isInterestBearingAndInterestRecalculationEnabled() || downPaymentEnabled) {
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
}
disburseLoan(command, isPaymentTypeApplicableForDisbursementCharge, paymentDetail, loan, currentUser, changes,
scheduleGeneratorDTO);
loan.adjustNetDisbursalAmount(amountToDisburse.getAmount());
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
LocalDate firstInstallmentDueDate = loan.fetchRepaymentScheduleInstallment(1).getDueDate();
if (loan.isInterestBearingAndInterestRecalculationEnabled()
&& (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || loan.isDisbursementMissed())) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
}
if (loan.isAutoRepaymentForDownPaymentEnabled() && !isWithoutAutoPayment) {
// updating linked savings account for auto down payment transaction for disbursement to savings account
if (isAccountTransfer && loan.shouldCreateStandingInstructionAtDisbursement()) {
final PortfolioAccountData linkedSavingsAccountData = this.accountAssociationsReadPlatformService
.retriveLoanLinkedAssociation(loanId);
final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true;
final boolean isExceptionForBalanceCheck = false;
BigDecimal disbursedAmountPercentageForDownPayment = loan.getLoanRepaymentScheduleDetail()
.getDisbursedAmountPercentageForDownPayment();
Money downPaymentMoney = Money.of(loan.getCurrency(),
MathUtil.percentageOf(amountToDisburse.getAmount(), disbursedAmountPercentageForDownPayment, 19));
if (loan.getLoanProduct().getInstallmentAmountInMultiplesOf() != null) {
downPaymentMoney = Money.roundToMultiplesOf(downPaymentMoney,
loan.getLoanProduct().getInstallmentAmountInMultiplesOf());
}
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate,
downPaymentMoney.getAmount(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
linkedSavingsAccountData.getId(), loan.getId(),
"To loan " + loan.getAccountNumber() + " from savings " + linkedSavingsAccountData.getAccountNo()
+ " Standing instruction transfer ",
locale, fmt, null, null, LoanTransactionType.DOWN_PAYMENT.getValue(), null, null,
AccountTransferType.LOAN_DOWN_PAYMENT.getValue(), null, null, ExternalId.empty(), null, null,
fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} else {
loanDownPaymentHandlerService.handleDownPayment(scheduleGeneratorDTO, command, disbursementTransaction, loan);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
}
}
}
}
if (!changes.isEmpty()) {
loan.updateLoanScheduleDependentDerivedFields();
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
createNote(loan, command, changes);
// auto create standing instruction
createStandingInstruction(loan);
}
final Set<LoanCharge> loanCharges = loan.getActiveCharges();
final Map<Long, BigDecimal> disBuLoanCharges = new HashMap<>();
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()
&& loanCharge.isChargePending()) {
disBuLoanCharges.put(loanCharge.getId(), loanCharge.amountOutstanding());
}
}
for (final Map.Entry<Long, BigDecimal> entrySet : disBuLoanCharges.entrySet()) {
final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanLinkedAssociation(loanId);
final SavingsAccount fromSavingsAccount = null;
final boolean isRegularTransaction = true;
final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.getId(), loanId, "Loan Charge Payment",
locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(), entrySet.getKey(), null,
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, ExternalId.empty(), null, null, fromSavingsAccount,
isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
}
updateRecurringCalendarDatesForInterestRecalculation(loan);
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
// Post Dated Checks
if (command.parameterExists("postDatedChecks")) {
// get repayment with post dates checks to update
Set<PostDatedChecks> postDatedChecks = this.repaymentWithPostDatedChecksAssembler.fromParsedJson(command.json(), loan);
updatePostDatedChecks(postDatedChecks);
}
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalBusinessEvent(loan));
Long disbursalTransactionId = null;
ExternalId disbursalTransactionExternalId = null;
if (!isAccountTransfer) {
// If accounting is not periodic accrual, the last transaction might be the accrual not the disbursement
LoanTransaction disbursalTransaction = Lists.reverse(loan.getLoanTransactions()).stream()
.filter(e -> LoanTransactionType.DISBURSEMENT.equals(e.getTypeOf())).findFirst().orElseThrow();
disbursalTransactionId = disbursalTransaction.getId();
disbursalTransactionExternalId = disbursalTransaction.getExternalId();
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalTransactionBusinessEvent(disbursalTransaction));
}
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loan.getId()) //
.withEntityExternalId(loan.getExternalId()) //
.withSubEntityId(disbursalTransactionId) //
.withSubEntityExternalId(disbursalTransactionExternalId) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes) //
.build();
}