diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java index d1fa67af8..c5817dd9b 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java @@ -3652,10 +3652,10 @@ public class ClientLoanIntegrationTest { loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); - addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "252.89"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2492.89", "36.02", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2517.23", "11.68", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "252.89"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2493.05", "35.86", "0.0", "252.89"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2491.72", "37.19", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2517.22", "11.69", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); repaymentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); @@ -3667,10 +3667,10 @@ public class ClientLoanIntegrationTest { loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); - addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "252.89"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2498.39", "30.52", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, "2517.26", "11.65", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "252.89"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2493.05", "35.86", "0.0", "252.89"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2491.72", "37.19", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2517.22", "11.69", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 1f5f13eeb..ed279e3a8 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -502,8 +502,7 @@ public class Loan extends AbstractPersistable { this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration; } - public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge) { - ChangedTransactionDetail changedTransactionDetail = null; + public void addLoanCharge(final LoanCharge loanCharge) { validateLoanIsNotClosed(loanCharge); @@ -554,27 +553,29 @@ public class Loan extends AbstractPersistable { } this.charges.add(loanCharge); this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory - .determineProcessor(this.transactionProcessingStrategy); // store Id's of existing loan transactions and existing reversed loan // transactions final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc()); - if (!loanCharge.isDueAtDisbursement()) { - final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); - changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), - allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), - getLastUserTransactionForChargeCalc()); - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - mapEntry.getValue().updateLoan(this); - } - // this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values()); - } - updateLoanSummaryDerivedFields(); + } + + public ChangedTransactionDetail reprocessTransactions() { + ChangedTransactionDetail changedTransactionDetail = null; + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory + .determineProcessor(this.transactionProcessingStrategy); + final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), + getLastUserTransactionForChargeCalc()); + for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { + mapEntry.getValue().updateLoan(this); + } + this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values()); + updateLoanSummaryDerivedFields(); return changedTransactionDetail; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index a8ae5b170..95c4a33e4 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -700,7 +700,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); } - + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance); @@ -790,9 +790,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail); } final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder(); - LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, bulkRepaymentCommand.getTransactionDate(), - singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail, bulkRepaymentCommand.getNote(), null, - isRecoveryRepayment, isAccountTransfer); + LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(loan, commandProcessingResultBuilder, + bulkRepaymentCommand.getTransactionDate(), singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail, + bulkRepaymentCommand.getNote(), null, isRecoveryRepayment, isAccountTransfer); transactionIds.add(loanTransaction.getId()); } @@ -1305,13 +1305,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge); - + boolean reprocessRequired = true; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) { runScheduleRecalculation(loan); + reprocessRequired = false; } updateOriginalSchedule(loan); + } + if (reprocessRequired) { + ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); + if (changedTransactionDetail != null) { + for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { + this.loanTransactionRepository.save(mapEntry.getValue()); + // update loan with references to the newly created + // transactions + loan.getLoanTransactions().add(mapEntry.getValue()); + this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); + } + } + saveLoanWithDataIntegrityViolationChecks(loan); } postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); @@ -1420,7 +1434,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); } - + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance); createLoanScheduleArchive(loan, scheduleGeneratorDTO); @@ -1447,36 +1461,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.loanChargeRepository.save(loanCharge); - final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge); + loan.addLoanCharge(loanCharge); /** * we want to apply charge transactions only for those loans charges * that are applied when a loan is active and the loan product uses * Upfront Accruals **/ - if (loan.status().isActive()) { - if (loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { - final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null, currentUser); - this.loanTransactionRepository.save(applyLoanChargeTransaction); - } - /*** - * 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) - ***/ - saveLoanWithDataIntegrityViolationChecks(loan); - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - this.loanTransactionRepository.save(mapEntry.getValue()); - // update loan with references to the newly created - // transactions - loan.getLoanTransactions().add(mapEntry.getValue()); - this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - } - + if (loan.status().isActive() && loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { + final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null, currentUser); + this.loanTransactionRepository.save(applyLoanChargeTransaction); } boolean isAppliedOnBackDate = false; if (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate())) { @@ -2403,13 +2397,29 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf runInterestRecalculation = runInterestRecalculation || overdueDTO.isRunInterestRecalculation(); } if (loan != null) { + boolean reprocessRequired = true; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (runInterestRecalculation && loan.isFeeCompoundingEnabledForInterestRecalculation()) { runScheduleRecalculation(loan); + reprocessRequired = false; } updateOriginalSchedule(loan); } + if (reprocessRequired) { + ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); + if (changedTransactionDetail != null) { + for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { + this.loanTransactionRepository.save(mapEntry.getValue()); + // update loan with references to the newly created + // transactions + loan.getLoanTransactions().add(mapEntry.getValue()); + this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); + } + } + saveLoanWithDataIntegrityViolationChecks(loan); + } + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && runInterestRecalculation @@ -2556,59 +2566,54 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .build(); } - private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate){ - final String json = command.json(); - final JsonElement element = this.fromApiJsonHelper.parse(json); + private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate) { + final String json = command.json(); + final JsonElement element = this.fromApiJsonHelper.parse(json); final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName); - if(disbursementDataArray == null || disbursementDataArray.size() == 0){ - final String errorMessage = "For this loan product, disbursement details must be provided"; - throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage); + if (disbursementDataArray == null || disbursementDataArray.size() == 0) { + final String errorMessage = "For this loan product, disbursement details must be provided"; + throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage); } final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element); - - loanApplicationCommandFromApiJsonHelper.validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal); + + loanApplicationCommandFromApiJsonHelper.validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, + principal); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - - @Override + + @Override @Transactional - public CommandProcessingResult addAndDeleteLoanDisburseDetails(Long loanId, JsonCommand command) { - + public CommandProcessingResult addAndDeleteLoanDisburseDetails(Long loanId, JsonCommand command) { + final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); final Map actualChanges = new LinkedHashMap<>(); - LocalDate expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate(); + LocalDate expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate(); validateMultiDisbursementData(command, expectedDisbursementDate); loan.updateDisbursementDetails(command, actualChanges); if (loan.getDisbursementDetails().isEmpty()) { - final String errorMessage = "For this loan product, disbursement details must be provided"; - throw new MultiDisbursementDataRequiredException( - LoanApiConstants.disbursementDataParameterName, - errorMessage); - } + final String errorMessage = "For this loan product, disbursement details must be provided"; + throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage); + } - if (loan.getDisbursementDetails().size() > loan.loanProduct() - .maxTrancheCount()) { - final String errorMessage = "Number of tranche shouldn't be greter than " - + loan.loanProduct().maxTrancheCount(); - throw new ExceedingTrancheCountException( - LoanApiConstants.disbursementDataParameterName, - errorMessage, loan.loanProduct().maxTrancheCount(), - loan.getDisbursementDetails().size()); - } - LoanDisbursementDetails updateDetails = null; - return processLoanDisbursementDetail(loan, loanId, command, updateDetails); - - } - - private CommandProcessingResult processLoanDisbursementDetail(final Loan loan, Long loanId, JsonCommand command, - LoanDisbursementDetails loanDisbursementDetails){ - final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), + if (loan.getDisbursementDetails().size() > loan.loanProduct().maxTrancheCount()) { + final String errorMessage = "Number of tranche shouldn't be greter than " + loan.loanProduct().maxTrancheCount(); + throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage, loan.loanProduct() + .maxTrancheCount(), loan.getDisbursementDetails().size()); + } + LoanDisbursementDetails updateDetails = null; + return processLoanDisbursementDetail(loan, loanId, command, updateDetails); + + } + + private CommandProcessingResult processLoanDisbursementDetail(final Loan loan, Long loanId, JsonCommand command, + LoanDisbursementDetails loanDisbursementDetails) { + final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( loan.getDisbursementDate(), loan, calendarInstance); @@ -2637,23 +2642,23 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, lastTransactionDate); - ChangedTransactionDetail changedTransactionDetail = null; + ChangedTransactionDetail changedTransactionDetail = null; AppUser currentUser = getAppUserIfPresent(); - - if(command.entityId() != null){ - changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command, + if (command.entityId() != null) { + + changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command, existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO, currentUser); - }else{ - Collection loanDisburseDetails = loan.getDisbursementDetails(); - BigDecimal setAmount = BigDecimal.ZERO; + } else { + Collection loanDisburseDetails = loan.getDisbursementDetails(); + BigDecimal setAmount = BigDecimal.ZERO; for (LoanDisbursementDetails details : loanDisburseDetails) { - setAmount = setAmount.add(details.principal()); + setAmount = setAmount.add(details.principal()); } - + loan.repaymentScheduleDetail().setPrincipal(setAmount); - - if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); } else { loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); @@ -2677,21 +2682,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .withClientId(loan.getClientId()) // .withGroupId(loan.getGroupId()) // .withLoanId(loanId) // - .with(changes).build(); - } - + .with(changes).build(); + } + @Override @Transactional - public CommandProcessingResult updateDisbursementDateAndAmountForTranche(final Long loanId, final Long disbursementId, final JsonCommand command) { + public CommandProcessingResult updateDisbursementDateAndAmountForTranche(final Long loanId, final Long disbursementId, + final JsonCommand command) { final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId); this.loanEventApiJsonValidator.validateUpdateDisbursementDateAndAmount(command.json(), loanDisbursementDetails); - - return processLoanDisbursementDetail(loan, loanId, command, loanDisbursementDetails); - + return processLoanDisbursementDetail(loan, loanId, command, loanDisbursementDetails); + } public LoanTransaction disburseLoanAmountToSavings(final Long loanId, Long loanChargeId, final JsonCommand command,