| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.fineract.portfolio.loanaccount.domain; |
| |
| import java.math.BigDecimal; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; |
| import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; |
| import org.apache.fineract.infrastructure.core.data.ApiParameterError; |
| import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; |
| import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; |
| import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; |
| import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; |
| import org.apache.fineract.infrastructure.core.service.DateUtils; |
| import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; |
| import org.apache.fineract.organisation.holiday.domain.Holiday; |
| import org.apache.fineract.organisation.holiday.domain.HolidayRepository; |
| import org.apache.fineract.organisation.holiday.domain.HolidayStatusType; |
| import org.apache.fineract.organisation.monetary.data.CurrencyData; |
| import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; |
| import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; |
| import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; |
| import org.apache.fineract.organisation.monetary.domain.Money; |
| import org.apache.fineract.organisation.workingdays.domain.WorkingDays; |
| import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; |
| import org.apache.fineract.portfolio.account.domain.AccountTransferRepository; |
| import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction; |
| import org.apache.fineract.portfolio.client.domain.Client; |
| import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; |
| import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY; |
| import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; |
| import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; |
| import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService; |
| import org.apache.fineract.portfolio.group.domain.Group; |
| import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; |
| import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; |
| import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData; |
| import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; |
| import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService; |
| import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; |
| import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; |
| import org.apache.fineract.portfolio.note.domain.Note; |
| import org.apache.fineract.portfolio.note.domain.NoteRepository; |
| import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; |
| import org.apache.fineract.useradministration.domain.AppUser; |
| import org.joda.time.LocalDate; |
| import org.joda.time.LocalDateTime; |
| import org.springframework.beans.factory.annotation.Autowired; |
| import org.springframework.dao.DataIntegrityViolationException; |
| import org.springframework.stereotype.Service; |
| import org.springframework.transaction.annotation.Transactional; |
| |
| @Service |
| public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { |
| |
| private final LoanAssembler loanAccountAssembler; |
| private final LoanRepository loanRepository; |
| private final LoanTransactionRepository loanTransactionRepository; |
| private final ConfigurationDomainService configurationDomainService; |
| private final HolidayRepository holidayRepository; |
| private final WorkingDaysRepositoryWrapper workingDaysRepository; |
| |
| private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper; |
| private final JournalEntryWritePlatformService journalEntryWritePlatformService; |
| private final NoteRepository noteRepository; |
| private final AccountTransferRepository accountTransferRepository; |
| private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; |
| private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository; |
| private final LoanAccrualPlatformService loanAccrualPlatformService; |
| private final PlatformSecurityContext context; |
| private final BusinessEventNotifierService businessEventNotifierService; |
| private final LoanUtilService loanUtilService; |
| |
| @Autowired |
| public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository, |
| final LoanTransactionRepository loanTransactionRepository, final NoteRepository noteRepository, |
| final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository, |
| final WorkingDaysRepositoryWrapper workingDaysRepository, |
| final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, |
| final JournalEntryWritePlatformService journalEntryWritePlatformService, |
| final AccountTransferRepository accountTransferRepository, |
| final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, |
| final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository, |
| final LoanAccrualPlatformService loanAccrualPlatformService, final PlatformSecurityContext context, |
| final BusinessEventNotifierService businessEventNotifierService, final LoanUtilService loanUtilService) { |
| this.loanAccountAssembler = loanAccountAssembler; |
| this.loanRepository = loanRepository; |
| this.loanTransactionRepository = loanTransactionRepository; |
| this.noteRepository = noteRepository; |
| this.configurationDomainService = configurationDomainService; |
| this.holidayRepository = holidayRepository; |
| this.workingDaysRepository = workingDaysRepository; |
| this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; |
| this.journalEntryWritePlatformService = journalEntryWritePlatformService; |
| this.accountTransferRepository = accountTransferRepository; |
| this.applicationCurrencyRepository = applicationCurrencyRepository; |
| this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository; |
| this.loanAccrualPlatformService = loanAccrualPlatformService; |
| this.context = context; |
| this.businessEventNotifierService = businessEventNotifierService; |
| this.loanUtilService = loanUtilService; |
| } |
| |
| @Transactional |
| @Override |
| public LoanTransaction makeRepayment(final Loan loan, final CommandProcessingResultBuilder builderResult, |
| final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, |
| final String txnExternalId, final boolean isRecoveryRepayment, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto, |
| Boolean isHolidayValidationDone) { |
| AppUser currentUser = getAppUserIfPresent(); |
| checkClientOrGroupActive(loan); |
| this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT, |
| constructEntityMap(BUSINESS_ENTITY.LOAN, loan)); |
| |
| // TODO: Is it required to validate transaction date with meeting dates |
| // if repayments is synced with meeting? |
| /* |
| * if(loan.isSyncDisbursementWithMeeting()){ // validate actual |
| * disbursement date against meeting date CalendarInstance |
| * calendarInstance = |
| * this.calendarInstanceRepository.findCalendarInstaneByLoanId |
| * (loan.getId(), CalendarEntityType.LOANS.getValue()); |
| * this.loanEventApiJsonValidator |
| * .validateRepaymentDateWithMeetingDate(transactionDate, |
| * calendarInstance); } |
| */ |
| |
| final List<Long> existingTransactionIds = new ArrayList<>(); |
| final List<Long> existingReversedTransactionIds = new ArrayList<>(); |
| |
| final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount); |
| LoanTransaction newRepaymentTransaction = null; |
| final LocalDateTime currentDateTime = DateUtils.getLocalDateTimeOfTenant(); |
| if (isRecoveryRepayment) { |
| newRepaymentTransaction = LoanTransaction.recoveryRepayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate, |
| txnExternalId, currentDateTime, currentUser); |
| } else { |
| newRepaymentTransaction = LoanTransaction.repayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate, |
| txnExternalId, currentDateTime, currentUser); |
| } |
| |
| LocalDate recalculateFrom = null; |
| if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { |
| recalculateFrom = transactionDate; |
| } |
| final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, |
| holidayDetailDto); |
| |
| final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, |
| defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment, |
| scheduleGeneratorDTO, currentUser, isHolidayValidationDone); |
| |
| saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction); |
| |
| /*** |
| * TODO Vishwas Batch save is giving me a |
| * HibernateOptimisticLockingFailureException, looping and saving for |
| * the time being, not a major issue for now as this loop is entered |
| * only in edge cases (when a payment is made before the latest payment |
| * recorded against the loan) |
| ***/ |
| |
| saveAndFlushLoanWithDataIntegrityViolationChecks(loan); |
| |
| if (changedTransactionDetail != null) { |
| for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { |
| saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); |
| // update loan with references to the newly created transactions |
| loan.getLoanTransactions().add(mapEntry.getValue()); |
| updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); |
| } |
| } |
| |
| if (StringUtils.isNotBlank(noteText)) { |
| final Note note = Note.loanTransactionNote(loan, newRepaymentTransaction, noteText); |
| this.noteRepository.save(note); |
| } |
| |
| postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); |
| |
| recalculateAccruals(loan); |
| |
| this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT, |
| constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newRepaymentTransaction)); |
| |
| builderResult.withEntityId(newRepaymentTransaction.getId()) // |
| .withOfficeId(loan.getOfficeId()) // |
| .withClientId(loan.getClientId()) // |
| .withGroupId(loan.getGroupId()); // |
| |
| return newRepaymentTransaction; |
| } |
| |
| private void saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction) { |
| try { |
| this.loanTransactionRepository.save(newRepaymentTransaction); |
| } catch (DataIntegrityViolationException e) { |
| final Throwable realCause = e.getCause(); |
| final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); |
| final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction"); |
| if (realCause.getMessage().toLowerCase().contains("external_id_unique")) { |
| baseDataValidator.reset().parameter("externalId").value(newRepaymentTransaction.getExternalId()) |
| .failWithCode("value.must.be.unique"); |
| } |
| if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", |
| "Validation errors exist.", dataValidationErrors); } |
| } |
| } |
| |
| private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) { |
| try { |
| List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments(); |
| for (LoanRepaymentScheduleInstallment installment : installments) { |
| if (installment.getId() == null) { |
| this.repaymentScheduleInstallmentRepository.save(installment); |
| } |
| } |
| this.loanRepository.saveAndFlush(loan); |
| } catch (final DataIntegrityViolationException e) { |
| final Throwable realCause = e.getCause(); |
| final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); |
| final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction"); |
| if (realCause.getMessage().toLowerCase().contains("external_id_unique")) { |
| baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique"); |
| } |
| if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", |
| "Validation errors exist.", dataValidationErrors); } |
| } |
| } |
| |
| @Override |
| public void saveLoanWithDataIntegrityViolationChecks(final Loan loan) { |
| try { |
| List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments(); |
| for (LoanRepaymentScheduleInstallment installment : installments) { |
| if (installment.getId() == null) { |
| this.repaymentScheduleInstallmentRepository.save(installment); |
| } |
| } |
| this.loanRepository.save(loan); |
| } catch (final DataIntegrityViolationException e) { |
| final Throwable realCause = e.getCause(); |
| final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); |
| final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction"); |
| if (realCause.getMessage().toLowerCase().contains("external_id_unique")) { |
| baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique"); |
| } |
| if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", |
| "Validation errors exist.", dataValidationErrors); } |
| } |
| } |
| |
| @Override |
| @Transactional |
| public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate, |
| final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final String txnExternalId, |
| final Integer transactionType, Integer installmentNumber) { |
| AppUser currentUser = getAppUserIfPresent(); |
| boolean isAccountTransfer = true; |
| checkClientOrGroupActive(loan); |
| this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_CHARGE_PAYMENT, |
| constructEntityMap(BUSINESS_ENTITY.LOAN, loan)); |
| final List<Long> existingTransactionIds = new ArrayList<>(); |
| final List<Long> existingReversedTransactionIds = new ArrayList<>(); |
| |
| final Money paymentAmout = Money.of(loan.getCurrency(), transactionAmount); |
| final LoanTransactionType loanTransactionType = LoanTransactionType.fromInt(transactionType); |
| |
| final LoanTransaction newPaymentTransaction = LoanTransaction.loanPayment(null, loan.getOffice(), paymentAmout, paymentDetail, |
| transactionDate, txnExternalId, loanTransactionType, DateUtils.getLocalDateTimeOfTenant(), currentUser); |
| |
| if (loanTransactionType.isRepaymentAtDisbursement()) { |
| loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds); |
| } else { |
| final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); |
| final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), |
| transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue()); |
| final WorkingDays workingDays = this.workingDaysRepository.findOne(); |
| final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); |
| final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); |
| HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, |
| allowTransactionsOnNonWorkingDay); |
| |
| loan.makeChargePayment(chargeId, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, |
| holidayDetailDTO, newPaymentTransaction, installmentNumber); |
| } |
| saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction); |
| saveAndFlushLoanWithDataIntegrityViolationChecks(loan); |
| |
| if (StringUtils.isNotBlank(noteText)) { |
| final Note note = Note.loanTransactionNote(loan, newPaymentTransaction, noteText); |
| this.noteRepository.save(note); |
| } |
| |
| postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); |
| recalculateAccruals(loan); |
| this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CHARGE_PAYMENT, |
| constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newPaymentTransaction)); |
| return newPaymentTransaction; |
| } |
| |
| private void postJournalEntries(final Loan loanAccount, final List<Long> existingTransactionIds, |
| final List<Long> existingReversedTransactionIds, boolean isAccountTransfer) { |
| |
| final MonetaryCurrency currency = loanAccount.getCurrency(); |
| final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); |
| |
| final Map<String, Object> accountingBridgeData = loanAccount.deriveAccountingBridgeData(applicationCurrency.toData(), |
| existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); |
| this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); |
| } |
| |
| private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { |
| final List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values()); |
| return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); |
| } |
| |
| private void checkClientOrGroupActive(final Loan loan) { |
| final Client client = loan.client(); |
| if (client != null) { |
| if (client.isNotActive()) { throw new ClientNotActiveException(client.getId()); } |
| } |
| final Group group = loan.group(); |
| if (group != null) { |
| if (group.isNotActive()) { throw new GroupNotActiveException(group.getId()); } |
| } |
| } |
| |
| @Override |
| public LoanTransaction makeRefund(final Long accountId, final CommandProcessingResultBuilder builderResult, |
| final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, |
| final String txnExternalId) { |
| AppUser currentUser = getAppUserIfPresent(); |
| boolean isAccountTransfer = true; |
| final Loan loan = this.loanAccountAssembler.assembleFrom(accountId); |
| checkClientOrGroupActive(loan); |
| this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REFUND, |
| constructEntityMap(BUSINESS_ENTITY.LOAN, loan)); |
| final List<Long> existingTransactionIds = new ArrayList<>(); |
| final List<Long> existingReversedTransactionIds = new ArrayList<>(); |
| |
| final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount); |
| final LoanTransaction newRefundTransaction = LoanTransaction.refund(loan.getOffice(), refundAmount, paymentDetail, transactionDate, |
| txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); |
| final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); |
| final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), |
| transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue()); |
| final WorkingDays workingDays = this.workingDaysRepository.findOne(); |
| final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); |
| |
| loan.makeRefund(newRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, |
| allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay); |
| |
| saveLoanTransactionWithDataIntegrityViolationChecks(newRefundTransaction); |
| this.loanRepository.save(loan); |
| |
| if (StringUtils.isNotBlank(noteText)) { |
| final Note note = Note.loanTransactionNote(loan, newRefundTransaction, noteText); |
| this.noteRepository.save(note); |
| } |
| |
| postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); |
| this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REFUND, |
| constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newRefundTransaction)); |
| builderResult.withEntityId(newRefundTransaction.getId()) // |
| .withOfficeId(loan.getOfficeId()) // |
| .withClientId(loan.getClientId()) // |
| .withGroupId(loan.getGroupId()); // |
| |
| return newRefundTransaction; |
| } |
| |
| @Transactional |
| @Override |
| public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDate transactionDate, final BigDecimal transactionAmount, |
| final PaymentDetail paymentDetail, final String noteText, final String txnExternalId) { |
| AppUser currentUser = getAppUserIfPresent(); |
| final Loan loan = this.loanAccountAssembler.assembleFrom(loanId); |
| checkClientOrGroupActive(loan); |
| boolean isAccountTransfer = true; |
| final List<Long> existingTransactionIds = new ArrayList<>(); |
| final List<Long> existingReversedTransactionIds = new ArrayList<>(); |
| final Money amount = Money.of(loan.getCurrency(), transactionAmount); |
| LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), amount, paymentDetail, transactionDate, |
| txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); |
| disbursementTransaction.updateLoan(loan); |
| loan.getLoanTransactions().add(disbursementTransaction); |
| saveLoanTransactionWithDataIntegrityViolationChecks(disbursementTransaction); |
| saveAndFlushLoanWithDataIntegrityViolationChecks(loan); |
| |
| if (StringUtils.isNotBlank(noteText)) { |
| final Note note = Note.loanTransactionNote(loan, disbursementTransaction, noteText); |
| this.noteRepository.save(note); |
| } |
| |
| postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); |
| return disbursementTransaction; |
| } |
| |
| @Override |
| public void reverseTransfer(final LoanTransaction loanTransaction) { |
| loanTransaction.reverse(); |
| saveLoanTransactionWithDataIntegrityViolationChecks(loanTransaction); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService |
| * #recalculateAccruals(org.apache.fineract.portfolio.loanaccount.domain.Loan) |
| */ |
| @Override |
| public void recalculateAccruals(Loan loan) { |
| LocalDate accruedTill = loan.getAccruedTill(); |
| if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !loan.repaymentScheduleDetail().isInterestRecalculationEnabled() |
| || accruedTill == null || loan.isNpa() || !loan.status().isActive()) { return; } |
| Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = new ArrayList<>(); |
| List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments(); |
| Long loanId = loan.getId(); |
| Long officeId = loan.getOfficeId(); |
| LocalDate accrualStartDate = null; |
| PeriodFrequencyType repaymentFrequency = loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType(); |
| Integer repayEvery = loan.repaymentScheduleDetail().getRepayEvery(); |
| LocalDate interestCalculatedFrom = loan.getInterestChargedFromDate(); |
| Long loanProductId = loan.productId(); |
| MonetaryCurrency currency = loan.getCurrency(); |
| ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); |
| CurrencyData currencyData = applicationCurrency.toData(); |
| Set<LoanCharge> loanCharges = loan.charges(); |
| |
| for (LoanRepaymentScheduleInstallment installment : installments) { |
| if (!accruedTill.isBefore(installment.getDueDate()) |
| || (accruedTill.isAfter(installment.getFromDate()) && !accruedTill.isAfter(installment.getDueDate()))) { |
| BigDecimal dueDateFeeIncome = BigDecimal.ZERO; |
| BigDecimal dueDatePenaltyIncome = BigDecimal.ZERO; |
| LocalDate chargesTillDate = installment.getDueDate(); |
| if (!accruedTill.isAfter(installment.getDueDate())) { |
| chargesTillDate = accruedTill; |
| } |
| |
| for (final LoanCharge loanCharge : loanCharges) { |
| if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), chargesTillDate)) { |
| if (loanCharge.isFeeCharge()) { |
| dueDateFeeIncome = dueDateFeeIncome.add(loanCharge.amount()); |
| } else if (loanCharge.isPenaltyCharge()) { |
| dueDatePenaltyIncome = dueDatePenaltyIncome.add(loanCharge.amount()); |
| } |
| } |
| } |
| LoanScheduleAccrualData accrualData = new LoanScheduleAccrualData(loanId, officeId, installment.getInstallmentNumber(), |
| accrualStartDate, repaymentFrequency, repayEvery, installment.getDueDate(), installment.getFromDate(), |
| installment.getId(), loanProductId, installment.getInterestCharged(currency).getAmount(), installment |
| .getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(), |
| installment.getInterestAccrued(currency).getAmount(), installment.getFeeAccrued(currency).getAmount(), installment |
| .getPenaltyAccrued(currency).getAmount(), currencyData, interestCalculatedFrom, installment |
| .getInterestWaived(currency).getAmount()); |
| loanScheduleAccrualDatas.add(accrualData); |
| |
| } |
| } |
| |
| if (!loanScheduleAccrualDatas.isEmpty()) { |
| String error = this.loanAccrualPlatformService.addPeriodicAccruals(accruedTill, loanScheduleAccrualDatas); |
| if (error.length() > 0) { |
| String globalisationMessageCode = "error.msg.accrual.exception"; |
| throw new GeneralPlatformDomainRuleException(globalisationMessageCode, error, error); |
| } |
| } |
| } |
| |
| private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { |
| final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); |
| if (transferTransaction != null) { |
| transferTransaction.updateToLoanTransaction(newLoanTransaction); |
| this.accountTransferRepository.save(transferTransaction); |
| } |
| } |
| |
| private AppUser getAppUserIfPresent() { |
| AppUser user = null; |
| if (this.context != null) { |
| user = this.context.getAuthenticatedUserIfPresent(); |
| } |
| return user; |
| } |
| |
| @Override |
| public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate, |
| BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId) { |
| final Loan loan = this.loanAccountAssembler.assembleFrom(accountId); |
| checkClientOrGroupActive(loan); |
| this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_REFUND, |
| constructEntityMap(BUSINESS_ENTITY.LOAN, loan)); |
| final List<Long> existingTransactionIds = new ArrayList<>(); |
| final List<Long> existingReversedTransactionIds = new ArrayList<>(); |
| AppUser currentUser = getAppUserIfPresent(); |
| |
| final Money refundAmount = Money.of(loan.getCurrency(), transactionAmount); |
| final LoanTransaction newRefundTransaction = LoanTransaction.refundForActiveLoan(loan.getOffice(), refundAmount, paymentDetail, |
| transactionDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); |
| final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); |
| final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), |
| transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue()); |
| final WorkingDays workingDays = this.workingDaysRepository.findOne(); |
| final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); |
| |
| loan.makeRefundForActiveLoan(newRefundTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, |
| existingReversedTransactionIds, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay); |
| |
| this.loanTransactionRepository.save(newRefundTransaction); |
| this.loanRepository.save(loan); |
| |
| if (StringUtils.isNotBlank(noteText)) { |
| final Note note = Note.loanTransactionNote(loan, newRefundTransaction, noteText); |
| this.noteRepository.save(note); |
| } |
| |
| postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false); |
| recalculateAccruals(loan); |
| this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REFUND, |
| constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, newRefundTransaction)); |
| |
| builderResult.withEntityId(newRefundTransaction.getId()) // |
| .withOfficeId(loan.getOfficeId()) // |
| .withClientId(loan.getClientId()) // |
| .withGroupId(loan.getGroupId()); // |
| |
| return newRefundTransaction; |
| } |
| |
| private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) { |
| Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1); |
| map.put(entityEvent, entity); |
| return map; |
| } |
| } |