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 6c36d1a1072..3c8677bb2d2 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 @@ -1945,7 +1945,6 @@ public Map loanApplicationWithdrawnByApplicant(final AppUser cur public Map loanApplicationApproval(final AppUser currentUser, final JsonCommand command, final JsonArray disbursementDataArray, final LoanLifecycleStateMachine loanLifecycleStateMachine) { - validateAccountStatus(LoanEvent.LOAN_APPROVED); final Map actualChanges = new LinkedHashMap<>(); @@ -2001,18 +2000,7 @@ public Map loanApplicationApproval(final AppUser currentUser, fi recalculateAllCharges(); if (loanProduct.isMultiDisburseLoan()) { - List currentDisbursementDetails = getDisbursementDetails(); - if (loanProduct.isDisallowExpectedDisbursements()) { - if (!currentDisbursementDetails.isEmpty()) { - final String errorMessage = "For this loan product, disbursement details are not allowed"; - throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage); - } - } else { - if (currentDisbursementDetails.isEmpty()) { - final String errorMessage = "For this loan product, disbursement details must be provided"; - throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage); - } - } + List currentDisbursementDetails = getLoanDisbursementDetails(); if (currentDisbursementDetails.size() > loanProduct.maxTrancheCount()) { final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount(); @@ -2064,6 +2052,22 @@ public Map loanApplicationApproval(final AppUser currentUser, fi } + private List getLoanDisbursementDetails() { + List currentDisbursementDetails = getDisbursementDetails(); + if (loanProduct.isDisallowExpectedDisbursements()) { + if (!currentDisbursementDetails.isEmpty()) { + final String errorMessage = "For this loan product, disbursement details are not allowed"; + throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage); + } + } else { + if (currentDisbursementDetails.isEmpty()) { + final String errorMessage = "For this loan product, disbursement details must be provided"; + throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage); + } + } + return currentDisbursementDetails; + } + private void compareApprovedToProposedPrincipal(BigDecimal approvedLoanAmount) { if (this.loanProduct().isDisallowExpectedDisbursements() && this.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 69a738d79a9..ba1dd77c50e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -547,98 +547,55 @@ public CommandProcessingResult approveGLIMLoanAppication(final Long loanId, fina @Transactional @Override public CommandProcessingResult approveApplication(final Long loanId, final JsonCommand command) { - final AppUser currentUser = getAppUserIfPresent(); - LocalDate expectedDisbursementDate = null; - - this.loanApplicationTransitionApiJsonValidator.validateApproval(command.json()); + loanApplicationTransitionApiJsonValidator.validateApproval(command.json()); Loan loan = retrieveLoanBy(loanId); + checkClientOrGroupActive(loan); - final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName); - - expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.expectedDisbursementDateParameterName); + LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.expectedDisbursementDateParameterName); if (expectedDisbursementDate == null) { expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate(); } + if (loan.loanProduct().isMultiDisburseLoan()) { - this.validateMultiDisbursementData(command, expectedDisbursementDate); + validateMultiDisbursementData(command, expectedDisbursementDate); } - checkClientOrGroupActive(loan); - Boolean isSkipRepaymentOnFirstMonth = false; - Integer numberOfDays = 0; - // validate expected disbursement date against meeting date + final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName); + + boolean isSkipRepaymentOnFirstMonth = false; + int numberOfDays = 0; if (loan.isSyncDisbursementWithMeeting() && (loan.isGroupLoan() || loan.isJLGLoan())) { - final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), - CalendarEntityType.LOANS.getValue()); - Calendar calendar = null; - if (calendarInstance != null) { - calendar = calendarInstance.getCalendar(); + Calendar calendar = getCalendarInstance(loan); + isSkipRepaymentOnFirstMonth = isLoanRepaymentsSyncWithMeeting(loan, calendar); + if (isSkipRepaymentOnFirstMonth) { + numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); } - // final Calendar calendar = calendarInstance.getCalendar(); - boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled(); - if (isSkipRepaymentOnFirstMonthEnabled) { - isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), calendar); - if (isSkipRepaymentOnFirstMonth) { - numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); - } - } - this.loanScheduleAssembler.validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar, - isSkipRepaymentOnFirstMonth, numberOfDays); - } - final Map changes = loan.loanApplicationApproval(currentUser, command, disbursementDataArray, - defaultLoanLifecycleStateMachine); + loanScheduleAssembler.validateDisbursementDateWithMeetingDates(expectedDisbursementDate, getCalendarInstance(loan), isSkipRepaymentOnFirstMonth, numberOfDays); - entityDatatableChecksWritePlatformService.runTheCheckForProduct(loanId, EntityTables.LOAN.getName(), - StatusEnum.APPROVE.getCode().longValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId()); + final Map changes = loan.loanApplicationApproval(currentUser, command, disbursementDataArray, defaultLoanLifecycleStateMachine); - if (!changes.isEmpty()) { + entityDatatableChecksWritePlatformService.runTheCheckForProduct(loanId, EntityTables.LOAN.getName(), StatusEnum.APPROVE.getCode().longValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId()); - // If loan approved amount less than loan demanded amount, then need - // to recompute the schedule - if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName) || changes.containsKey("recalculateLoanSchedule") - || changes.containsKey("expectedDisbursementDate")) { - LocalDate recalculateFrom = null; - ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); - loan.regenerateRepaymentSchedule(scheduleGeneratorDTO); + if (!changes.isEmpty()) { + if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName) || changes.containsKey("recalculateLoanSchedule") || changes.containsKey("expectedDisbursementDate")) { + loan.regenerateRepaymentSchedule(loanUtilService.buildScheduleGeneratorDTO(loan, null)); } if (loan.isTopup() && loan.getClientId() != null) { - final Long loanIdToClose = loan.getTopupLoanDetails().getLoanIdToClose(); - final Loan loanToClose = this.loanRepositoryWrapper.findNonClosedLoanThatBelongsToClient(loanIdToClose, loan.getClientId()); - if (loanToClose == null) { - throw new GeneralPlatformDomainRuleException("error.msg.loan.to.be.closed.with.topup.is.not.active", - "Loan to be closed with this topup is not active."); - } - - final LocalDate lastUserTransactionOnLoanToClose = loanToClose.getLastUserTransactionDate(); - if (DateUtils.isBefore(loan.getDisbursementDate(), lastUserTransactionOnLoanToClose)) { - throw new GeneralPlatformDomainRuleException( - "error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed", - "Disbursal date of this loan application " + loan.getDisbursementDate() - + " should be after last transaction date of loan to be closed " + lastUserTransactionOnLoanToClose); - } - BigDecimal loanOutstanding = this.loanReadPlatformService - .retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, expectedDisbursementDate).getAmount(); - final BigDecimal firstDisbursalAmount = loan.getFirstDisbursalAmount(); - if (loanOutstanding.compareTo(firstDisbursalAmount) > 0) { - throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed", - "Topup loan amount should be greater than outstanding amount of loan to be closed."); - } - BigDecimal netDisbursalAmount = loan.getApprovedPrincipal().subtract(loanOutstanding); - loan.adjustNetDisbursalAmount(netDisbursalAmount); + validateTopupLoan(loan); } - loan = loanRepository.saveAndFlush(loan); + loan = this.loanRepository.saveAndFlush(loan); final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { final Note note = Note.loanNote(loan, noteText); changes.put("note", noteText); - this.noteRepository.save(note); + noteRepository.save(note); } businessEventNotifierService.notifyPostBusinessEvent(new LoanApprovedBusinessEvent(loan)); @@ -656,6 +613,40 @@ public CommandProcessingResult approveApplication(final Long loanId, final JsonC .build(); } + private Calendar getCalendarInstance(Loan loan) { + CalendarInstance calendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); + return calendarInstance != null ? calendarInstance.getCalendar() : null; + } + + private boolean isLoanRepaymentsSyncWithMeeting(Loan loan, Calendar calendar) { + return configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled() && loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), calendar); + } + + private void validateTopupLoan(Loan loan) { + final Long loanIdToClose = loan.getTopupLoanDetails().getLoanIdToClose(); + final Loan loanToClose = loanRepositoryWrapper.findNonClosedLoanThatBelongsToClient(loanIdToClose, loan.getClientId()); + if (loanToClose == null) { + throw new GeneralPlatformDomainRuleException("error.msg.loan.to.be.closed.with.topup.is.not.active", "Loan to be closed with this topup is not active."); + } + + final LocalDate lastUserTransactionOnLoanToClose = loanToClose.getLastUserTransactionDate(); + if (DateUtils.isBefore(loan.getDisbursementDate(), lastUserTransactionOnLoanToClose)) { + throw new GeneralPlatformDomainRuleException( + "error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed", + "Disbursal date of this loan application " + loan.getDisbursementDate() + " should be after last transaction date of loan to be closed " + lastUserTransactionOnLoanToClose); + } + + BigDecimal loanOutstanding = loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, expectedDisbursementDate).getAmount(); + final BigDecimal firstDisbursalAmount = loan.getFirstDisbursalAmount(); + if (loanOutstanding.compareTo(firstDisbursalAmount) > 0) { + throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed", + "Topup loan amount should be greater than outstanding amount of loan to be closed."); + } + + BigDecimal netDisbursalAmount = loan.getApprovedPrincipal().subtract(loanOutstanding); + loan.adjustNetDisbursalAmount(netDisbursalAmount); + } + @Transactional @Override public CommandProcessingResult undoGLIMLoanApplicationApproval(final Long loanId, final JsonCommand command) { @@ -853,16 +844,12 @@ private Loan retrieveLoanBy(final Long loanId) { private void checkClientOrGroupActive(final Loan loan) { final Client client = loan.client(); - if (client != null) { - if (client.isNotActive()) { - throw new ClientNotActiveException(client.getId()); - } + if (client != null && client.isNotActive()) { + throw new ClientNotActiveException(client.getId()); } final Group group = loan.group(); - if (group != null) { - if (group.isNotActive()) { - throw new GroupNotActiveException(group.getId()); - } + if (group != null && group.isNotActive()) { + throw new GroupNotActiveException(group.getId()); } }