diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobParameter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobParameter.java new file mode 100644 index 000000000..06fafb791 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobParameter.java @@ -0,0 +1,90 @@ +/** + * 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.infrastructure.jobs.domain; + +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.util.Objects; + +@Entity +@Table(name = "job_parameters") +public class JobParameter extends AbstractPersistableCustom { + + @Column(name = "job_id", nullable = false) + private Long jobId; + + @Column(name = "parameter_name",nullable = true) + private String parameterName; + + @Column(name = "parameter_value",nullable = true) + private String parameterValue; + + + public JobParameter(final Long jobId, final String parameterName, final String parameterValue) { + this.jobId = jobId; + this.parameterName = parameterName; + this.parameterValue = parameterValue; + } + + public static JobParameter getInstance(final Long jobId, final String parameterName, final String parameterValue){ + return new JobParameter(jobId,parameterName,parameterValue); + } + + public Long getJobId() { + return jobId; + } + + public void setJobId(final Long jobId) { + this.jobId = jobId; + } + + public String getParameterName() { + return parameterName; + } + + public void setParameterName(final String parameterName) { + this.parameterName = parameterName; + } + + public String getParameterValue() { + return parameterValue; + } + + public void setParameterValue(final String parameterValue) { + this.parameterValue = parameterValue; + } + + + @Override + public boolean equals(Object obj) { + if (!obj.getClass().equals(getClass())) return false; + JobParameter jobParameter = (JobParameter) obj; + return jobParameter.getJobId().equals(this.getJobId()) + && jobParameter.getParameterName().equals(this.getParameterName()) + && jobParameter.getParameterValue().equals(this.getParameterValue()); + } + + @Override + public int hashCode() { + return Objects.hash(jobId,parameterName,parameterValue); + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobParameterRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobParameterRepository.java new file mode 100644 index 000000000..4e6ac59c7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobParameterRepository.java @@ -0,0 +1,32 @@ +/** + * 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.infrastructure.jobs.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface JobParameterRepository extends JpaRepository, JpaSpecificationExecutor { + + @Query("select jobParameter from JobParameter jobParameter where jobParameter.jobId=:jobId") + List findJobParametersByJobId(@Param("jobId") Long jobId); +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java index d18538705..0290839a2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.infrastructure.jobs.service; + import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; @@ -25,7 +26,7 @@ import java.util.HashMap; import java.util.List; import java.util.Properties; import java.util.TimeZone; - +import java.util.Map; import javax.annotation.PostConstruct; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; @@ -33,6 +34,8 @@ import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerE import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.jobs.annotation.CronMethodParser; import org.apache.fineract.infrastructure.jobs.annotation.CronMethodParser.ClassMethodNamesPair; +import org.apache.fineract.infrastructure.jobs.domain.JobParameter; +import org.apache.fineract.infrastructure.jobs.domain.JobParameterRepository; import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail; import org.apache.fineract.infrastructure.jobs.domain.SchedulerDetail; import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException; @@ -78,6 +81,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi private SchedulerJobListener schedulerJobListener; private SchedulerStopListener schedulerStopListener; private SchedulerTriggerListener globalSchedulerTriggerListener; + private JobParameterRepository jobParameterRepository; private final HashMap schedulers = new HashMap<>(4); @@ -111,6 +115,11 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi this.globalSchedulerTriggerListener = globalTriggerListener; } + @Autowired + public void setJobParameterRepository(JobParameterRepository jobParameterRepository){ + this.jobParameterRepository=jobParameterRepository; + } + @PostConstruct public void loadAllJobs() { final List allTenants = this.tenantDetailsService.findAllTenants(); @@ -354,6 +363,14 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi return jobDetailFactoryBean.getObject(); } + public Map getJobParameter(ScheduledJobDetail scheduledJobDetail){ + List jobParameterList= jobParameterRepository.findJobParametersByJobId(scheduledJobDetail.getId()); + Map jobParameterMap=new HashMap<>(); + for (JobParameter jobparameter:jobParameterList) { + jobParameterMap.put(jobparameter.getParameterName(),jobparameter.getParameterValue()); + } + return jobParameterMap; + } private Object getBeanObject(final Class classType) throws ClassNotFoundException { final List> typesList = new ArrayList<>(); final Class[] interfaceType = classType.getInterfaces(); @@ -419,4 +436,4 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi final JobKey JobKey = new JobKey(keyParams[0], keyParams[1]); return JobKey; } -} +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java old mode 100755 new mode 100644 index 2fd6c62ea..84e7d66f9 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.service; import java.math.BigDecimal; import java.util.Collection; import java.util.Date; +import java.util.List; import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.SearchParameters; @@ -45,7 +46,7 @@ public interface LoanReadPlatformService { LoanAccountData retrieveOne(Long loanId); LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData, - Collection disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges); + Collection disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges); Collection retrieveLoanTransactions(Long loanId); @@ -84,7 +85,7 @@ public interface LoanReadPlatformService { /* * musoni-specific at present - will find overdue scheduled installments * that have a special 'overdue charge' associated with the loan product. - * + * * The 'overdue-charge' is only ever applied once to an installment and as a * result overdue installments with this charge already applied are not * returned. @@ -111,6 +112,8 @@ public interface LoanReadPlatformService { Collection fetchLoansForInterestRecalculation(); + List fetchLoansForInterestRecalculation(Integer pageSize, Long maxLoanIdInList, String officeHierarchy); + LoanTransactionData retrieveLoanPrePaymentTemplate(Long loanId, LocalDate onDate); Collection retrieveWaiverLoanTransactions(Long loanId); @@ -124,17 +127,20 @@ public interface LoanReadPlatformService { PaidInAdvanceData retrieveTotalPaidInAdvance(Long loanId); LoanTransactionData retrieveRefundByCashTemplate(Long loanId); - + Collection retrieveLoanInterestRatePeriodData(LoanAccountData loan); Collection retrieveLoanIdsWithPendingIncomePostingTransactions(); LoanTransactionData retrieveLoanForeclosureTemplate(final Long loanId, final LocalDate transactionDate); + LoanAccountData retrieveLoanByLoanAccount(String loanAccountNumber); Long retrieveLoanIdByAccountNumber(String loanAccountNumber); String retrieveAccountNumberByAccountId(Long accountId); + Integer retrieveNumberOfActiveLoans(); + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java old mode 100755 new mode 100644 index f9e4480d0..c64227488 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -30,6 +30,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Collections; import org.apache.commons.lang.StringUtils; import org.apache.fineract.accounting.common.AccountingRuleType; @@ -159,17 +160,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { @Autowired public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, - final LoanProductReadPlatformService loanProductReadPlatformService, final ClientReadPlatformService clientReadPlatformService, - final GroupReadPlatformService groupReadPlatformService, final LoanDropdownReadPlatformService loanDropdownReadPlatformService, - final FundReadPlatformService fundReadPlatformService, final ChargeReadPlatformService chargeReadPlatformService, - final CodeValueReadPlatformService codeValueReadPlatformService, final RoutingDataSource dataSource, - final CalendarReadPlatformService calendarReadPlatformService, final StaffReadPlatformService staffReadPlatformService, - final PaymentTypeReadPlatformService paymentTypeReadPlatformService, - final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, - final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanUtilService loanUtilService, - final ConfigurationDomainService configurationDomainService, - final AccountDetailsReadPlatformService accountDetailsReadPlatformService, - final LoanRepositoryWrapper loanRepositoryWrapper, final ColumnValidator columnValidator) { + final LoanProductReadPlatformService loanProductReadPlatformService, final ClientReadPlatformService clientReadPlatformService, + final GroupReadPlatformService groupReadPlatformService, final LoanDropdownReadPlatformService loanDropdownReadPlatformService, + final FundReadPlatformService fundReadPlatformService, final ChargeReadPlatformService chargeReadPlatformService, + final CodeValueReadPlatformService codeValueReadPlatformService, final RoutingDataSource dataSource, + final CalendarReadPlatformService calendarReadPlatformService, final StaffReadPlatformService staffReadPlatformService, + final PaymentTypeReadPlatformService paymentTypeReadPlatformService, + final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanUtilService loanUtilService, + final ConfigurationDomainService configurationDomainService, + final AccountDetailsReadPlatformService accountDetailsReadPlatformService, + final LoanRepositoryWrapper loanRepositoryWrapper, final ColumnValidator columnValidator) { this.context = context; this.loanRepositoryWrapper = loanRepositoryWrapper ; this.applicationCurrencyRepository = applicationCurrencyRepository; @@ -216,27 +217,27 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { throw new LoanNotFoundException(loanId); } } - + @Override public LoanAccountData retrieveLoanByLoanAccount(String loanAccountNumber) { - - //final AppUser currentUser = this.context.authenticatedUser(); - this.context.authenticatedUser(); - final LoanMapper rm = new LoanMapper(); - String sql="select "+rm.loanSchema()+" where l.account_no=?"; - + //final AppUser currentUser = this.context.authenticatedUser(); + this.context.authenticatedUser(); + final LoanMapper rm = new LoanMapper(); + + String sql="select "+rm.loanSchema()+" where l.account_no=?"; + + + return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanAccountNumber }); - return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanAccountNumber }); - } @Override public LoanScheduleData retrieveRepaymentSchedule(final Long loanId, - final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection disbursementData, - boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) { + final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection disbursementData, + boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) { try { this.context.authenticatedUser(); @@ -250,8 +251,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { throw new LoanNotFoundException(loanId); } } - - + + @Override public Collection retrieveLoanTransactions(final Long loanId) { @@ -453,14 +454,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { BigDecimal adjustedChargeAmount = adjustPrepayInstallmentCharge(loan, onDate); return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount().subtract(adjustedChargeAmount), loanRepaymentScheduleInstallment - .getPrincipalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getInterestOutstanding(currency) - .getAmount(), loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount().subtract(adjustedChargeAmount), + .getPrincipalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getInterestOutstanding(currency) + .getAmount(), loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount().subtract(adjustedChargeAmount), loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion, paymentOptions, null, null, null, outstandingLoanBalance, false); } - private BigDecimal adjustPrepayInstallmentCharge(Loan loan, final LocalDate onDate) { - BigDecimal chargeAmount = BigDecimal.ZERO; + private BigDecimal adjustPrepayInstallmentCharge(Loan loan, final LocalDate onDate) { + BigDecimal chargeAmount = BigDecimal.ZERO; /*for(LoanCharge loanCharge: loan.charges()){ if(loanCharge.isInstalmentFee() && loanCharge.getCharge().getChargeCalculation()==ChargeCalculationType.FLAT.getValue()){ for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) { @@ -475,8 +476,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } } }*/ - return chargeAmount; - } + return chargeAmount; + } @Override public LoanTransactionData retrieveWaiveInterestDetails(final Long loanId) { @@ -529,8 +530,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { if (paymentDetailsRequired) { paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); } - return LoanTransactionData.LoanTransactionDataForDisbursalTemplate(transactionType, loan.getExpectedDisbursedOnLocalDateForTemplate(), loan.getDisburseAmountForTemplate(), - paymentOptions, loan.retriveLastEmiAmount(), loan.getNextPossibleRepaymentDateForRescheduling()); + return LoanTransactionData.LoanTransactionDataForDisbursalTemplate(transactionType, loan.getExpectedDisbursedOnLocalDateForTemplate(), loan.getDisburseAmountForTemplate(), + paymentOptions, loan.retriveLastEmiAmount(), loan.getNextPossibleRepaymentDateForRescheduling()); } @@ -1039,7 +1040,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { private BigDecimal totalPaidFeeCharges; public LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, - Collection disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) { + Collection disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) { this.currency = repaymentScheduleRelatedLoanData.getCurrency(); this.disbursement = repaymentScheduleRelatedLoanData.disbursementData(); this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement(); @@ -1495,7 +1496,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { @Override public Collection retrieveAllLoansWithOverdueInstallments(final Long penaltyWaitPeriod, - final Boolean backdatePenalties) { + final Boolean backdatePenalties) { final MusoniOverdueLoanScheduleMapper rm = new MusoniOverdueLoanScheduleMapper(); final StringBuilder sqlBuilder = new StringBuilder(400); @@ -1614,13 +1615,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { if(organisationStartDate != null){ sqlBuilder.append(" and ls.duedate > :organisationstartdate "); } - sqlBuilder.append(" order by loan.id,ls.duedate "); + sqlBuilder.append(" order by loan.id,ls.duedate "); Map paramMap = new HashMap<>(3); paramMap.put("active", LoanStatus.ACTIVE.getValue()); paramMap.put("type", AccountingRuleType.ACCRUAL_PERIODIC.getValue()); paramMap.put("organisationstartdate", formatter.print(new LocalDate(organisationStartDate))); - - return this.namedParameterJdbcTemplate.query(sqlBuilder.toString(), paramMap, mapper); + + return this.namedParameterJdbcTemplate.query(sqlBuilder.toString(), paramMap, mapper); } @Override @@ -1641,7 +1642,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { if(organisationStartDate != null){ sqlBuilder.append(" and ls.duedate > :organisationstartdate "); } - sqlBuilder.append(" order by loan.id,ls.duedate "); + sqlBuilder.append(" order by loan.id,ls.duedate "); Map paramMap = new HashMap<>(4); paramMap.put("active", LoanStatus.ACTIVE.getValue()); paramMap.put("type", AccountingRuleType.ACCRUAL_PERIODIC.getValue()); @@ -1844,6 +1845,52 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } } + @Override + public List fetchLoansForInterestRecalculation(Integer pageSize, Long maxLoanIdInList, String officeHierarchy) { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("SELECT ml.id FROM m_loan ml "); + sqlBuilder.append(" left join m_client mc on mc.id = ml.client_id "); + sqlBuilder.append(" left join m_office o on mc.office_id = o.id "); + sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id "); + sqlBuilder.append(" LEFT JOIN m_loan_disbursement_detail dd on dd.loan_id=ml.id and dd.disbursedon_date is null "); + // For Floating rate changes + sqlBuilder + .append(" left join m_product_loan_floating_rates pfr on ml.product_id = pfr.loan_product_id and ml.is_floating_interest_rate = 1"); + sqlBuilder.append(" left join m_floating_rates fr on pfr.floating_rates_id = fr.id"); + sqlBuilder.append(" left join m_floating_rates_periods frp on fr.id = frp.floating_rates_id "); + sqlBuilder.append(" left join m_loan_reschedule_request lrr on lrr.loan_id = ml.id"); + // this is to identify the applicable rates when base rate is changed + sqlBuilder.append(" left join m_floating_rates bfr on bfr.is_base_lending_rate = 1"); + sqlBuilder.append(" left join m_floating_rates_periods bfrp on bfr.id = bfrp.floating_rates_id and bfrp.created_date >= ?"); + sqlBuilder.append(" WHERE ml.loan_status_id = ? "); + sqlBuilder.append(" and ml.is_npa = 0 "); + sqlBuilder.append(" and (("); + sqlBuilder.append("ml.interest_recalculation_enabled = 1 "); + sqlBuilder.append(" and (ml.interest_recalcualated_on is null or ml.interest_recalcualated_on <> ?)"); + sqlBuilder.append(" and (("); + sqlBuilder.append(" mr.completed_derived is false "); + sqlBuilder.append(" and mr.duedate < ? )"); + sqlBuilder.append(" or dd.expected_disburse_date < ? )) "); + sqlBuilder.append(" or ("); + sqlBuilder.append(" fr.is_active = 1 and frp.is_active = 1"); + sqlBuilder.append(" and (frp.created_date >= ? or "); + sqlBuilder.append("(bfrp.id is not null and frp.is_differential_to_base_lending_rate = 1 and frp.from_date >= bfrp.from_date)) "); + sqlBuilder.append("and lrr.loan_id is null"); + sqlBuilder.append(" ))"); + sqlBuilder.append(" and ml.id >= ? and o.hierarchy like ? "); + sqlBuilder.append(" group by ml.id "); + sqlBuilder.append(" limit ? "); + try { + String currentdate = formatter.print(DateUtils.getLocalDateOfTenant()); + // will look only for yesterday modified rates + String yesterday = formatter.print(DateUtils.getLocalDateOfTenant().minusDays(1)); + return Collections.synchronizedList(this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, new Object[] { yesterday, + LoanStatus.ACTIVE.getValue(), currentdate, currentdate, currentdate, yesterday,maxLoanIdInList,officeHierarchy,pageSize })); + } catch (final EmptyResultDataAccessException e) { + return null; + } + } + @Override public Collection retrieveWaiverLoanTransactions(final Long loanId) { try { @@ -2076,17 +2123,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { @Override public Collection retrieveLoanIdsWithPendingIncomePostingTransactions() { StringBuilder sqlBuilder = new StringBuilder() - .append(" select distinct loan.id ") - .append(" from m_loan as loan ") - .append(" inner join m_loan_recalculation_details as recdet on (recdet.loan_id = loan.id and recdet.is_compounding_to_be_posted_as_transaction is not null and recdet.is_compounding_to_be_posted_as_transaction = 1) ") - .append(" inner join m_loan_repayment_schedule as repsch on repsch.loan_id = loan.id ") - .append(" inner join m_loan_interest_recalculation_additional_details as adddet on adddet.loan_repayment_schedule_id = repsch.id ") - .append(" left join m_loan_transaction as trans on (trans.is_reversed <> 1 and trans.transaction_type_enum = 19 and trans.loan_id = loan.id and trans.transaction_date = adddet.effective_date) ") - .append(" where loan.loan_status_id = 300 ") - .append(" and loan.is_npa = 0 ") - .append(" and adddet.effective_date is not null ") - .append(" and trans.transaction_date is null ") - .append(" and adddet.effective_date < ? "); + .append(" select distinct loan.id ") + .append(" from m_loan as loan ") + .append(" inner join m_loan_recalculation_details as recdet on (recdet.loan_id = loan.id and recdet.is_compounding_to_be_posted_as_transaction is not null and recdet.is_compounding_to_be_posted_as_transaction = 1) ") + .append(" inner join m_loan_repayment_schedule as repsch on repsch.loan_id = loan.id ") + .append(" inner join m_loan_interest_recalculation_additional_details as adddet on adddet.loan_repayment_schedule_id = repsch.id ") + .append(" left join m_loan_transaction as trans on (trans.is_reversed <> 1 and trans.transaction_type_enum = 19 and trans.loan_id = loan.id and trans.transaction_date = adddet.effective_date) ") + .append(" where loan.loan_status_id = 300 ") + .append(" and loan.is_npa = 0 ") + .append(" and adddet.effective_date is not null ") + .append(" and trans.transaction_date is null ") + .append(" and adddet.effective_date < ? "); try { String currentdate = formatter.print(DateUtils.getLocalDateOfTenant()); return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, new Object[] { currentdate }); @@ -2120,11 +2167,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, outStandingAmount.getAmount(), loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getInterestOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment - .getFeeChargesOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding( - currency).getAmount(), null, unrecognizedIncomePortion, paymentTypeOptions, null, null, null, + .getFeeChargesOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding( + currency).getAmount(), null, unrecognizedIncomePortion, paymentTypeOptions, null, null, null, outstandingLoanBalance, isReversed); } - + private static final class CurrencyMapper implements RowMapper { @Override @@ -2139,7 +2186,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } } - + private static final class RepaymentTransactionTemplateMapper implements RowMapper { private CurrencyMapper currencyMapper = new CurrencyMapper(); @@ -2199,16 +2246,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } - @Override - public Long retrieveLoanIdByAccountNumber(String loanAccountNumber) { - try { - return this.jdbcTemplate.queryForObject("select l.id from m_loan l where l.account_no = ?", - new Object[] { loanAccountNumber }, Long.class); + @Override + public Long retrieveLoanIdByAccountNumber(String loanAccountNumber) { + try { + return this.jdbcTemplate.queryForObject("select l.id from m_loan l where l.account_no = ?", + new Object[] { loanAccountNumber }, Long.class); + + } catch (final EmptyResultDataAccessException e) { + return null; + } + } - } catch (final EmptyResultDataAccessException e) { - return null; - } - } public String retrieveAccountNumberByAccountId(Long accountId) { try { @@ -2218,4 +2266,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { throw new LoanNotFoundException(accountId); } } + + + @Override + public Integer retrieveNumberOfActiveLoans() { + final String sql="select count(*) from m_loan"; + return this.jdbcTemplate.queryForObject(sql,Integer.class); + } } + diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularService.java index 056447c35..e45185ccb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularService.java @@ -19,6 +19,10 @@ package org.apache.fineract.portfolio.loanaccount.service; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; +import org.apache.fineract.organisation.office.data.OfficeData; +import org.apache.fineract.organisation.office.domain.Office; + +import java.util.Map; public interface LoanSchedularService { @@ -27,4 +31,8 @@ public interface LoanSchedularService { void recalculateInterest() throws JobExecutionException; -} + void recalculateInterest(final OfficeData office, final int threadPoolSize, final int batchSize); + + void recalculateInterest(@SuppressWarnings("unused") final Map jobParameters); + +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularServiceImpl.java index 50de5d2e9..59196cde7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSchedularServiceImpl.java @@ -18,12 +18,8 @@ */ package org.apache.fineract.portfolio.loanaccount.service; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; +import java.util.*; +import java.util.concurrent.*; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.data.ApiParameterError; @@ -33,83 +29,94 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.jobs.annotation.CronTarget; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.apache.fineract.organisation.office.data.OfficeData; +import org.apache.fineract.organisation.office.exception.OfficeNotFoundException; +import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.dao.CannotAcquireLockException; import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; @Service public class LoanSchedularServiceImpl implements LoanSchedularService { - private final static Logger logger = LoggerFactory.getLogger(LoanSchedularServiceImpl.class); - private final ConfigurationDomainService configurationDomainService; - private final LoanReadPlatformService loanReadPlatformService; - private final LoanWritePlatformService loanWritePlatformService; + private final static Logger logger = LoggerFactory.getLogger(LoanSchedularServiceImpl.class); + private final ConfigurationDomainService configurationDomainService; + private final LoanReadPlatformService loanReadPlatformService; + private final LoanWritePlatformService loanWritePlatformService; + private final OfficeReadPlatformService officeReadPlatformService; + private final ApplicationContext applicationContext; - @Autowired - public LoanSchedularServiceImpl(final ConfigurationDomainService configurationDomainService, - final LoanReadPlatformService loanReadPlatformService, final LoanWritePlatformService loanWritePlatformService) { - this.configurationDomainService = configurationDomainService; - this.loanReadPlatformService = loanReadPlatformService; - this.loanWritePlatformService = loanWritePlatformService; - } + @Autowired + public LoanSchedularServiceImpl(final ConfigurationDomainService configurationDomainService, + final LoanReadPlatformService loanReadPlatformService, final LoanWritePlatformService loanWritePlatformService, + final OfficeReadPlatformService officeReadPlatformService, + final ApplicationContext applicationContext) { + this.configurationDomainService = configurationDomainService; + this.loanReadPlatformService = loanReadPlatformService; + this.loanWritePlatformService = loanWritePlatformService; + this.officeReadPlatformService = officeReadPlatformService; + this.applicationContext=applicationContext; + } - @Override - @CronTarget(jobName = JobName.APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT) - public void applyChargeForOverdueLoans() throws JobExecutionException { + @Override + @CronTarget(jobName = JobName.APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT) + public void applyChargeForOverdueLoans() throws JobExecutionException { - final Long penaltyWaitPeriodValue = this.configurationDomainService.retrievePenaltyWaitPeriod(); - final Boolean backdatePenalties = this.configurationDomainService.isBackdatePenaltiesEnabled(); - final Collection overdueLoanScheduledInstallments = this.loanReadPlatformService - .retrieveAllLoansWithOverdueInstallments(penaltyWaitPeriodValue,backdatePenalties); + final Long penaltyWaitPeriodValue = this.configurationDomainService.retrievePenaltyWaitPeriod(); + final Boolean backdatePenalties = this.configurationDomainService.isBackdatePenaltiesEnabled(); + final Collection overdueLoanScheduledInstallments = this.loanReadPlatformService + .retrieveAllLoansWithOverdueInstallments(penaltyWaitPeriodValue,backdatePenalties); - if (!overdueLoanScheduledInstallments.isEmpty()) { - final StringBuilder sb = new StringBuilder(); - final Map> overdueScheduleData = new HashMap<>(); - for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduledInstallments) { - if (overdueScheduleData.containsKey(overdueInstallment.getLoanId())) { - overdueScheduleData.get(overdueInstallment.getLoanId()).add(overdueInstallment); - } else { - Collection loanData = new ArrayList<>(); - loanData.add(overdueInstallment); - overdueScheduleData.put(overdueInstallment.getLoanId(), loanData); - } - } + if (!overdueLoanScheduledInstallments.isEmpty()) { + final StringBuilder sb = new StringBuilder(); + final Map> overdueScheduleData = new HashMap<>(); + for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduledInstallments) { + if (overdueScheduleData.containsKey(overdueInstallment.getLoanId())) { + overdueScheduleData.get(overdueInstallment.getLoanId()).add(overdueInstallment); + } else { + Collection loanData = new ArrayList<>(); + loanData.add(overdueInstallment); + overdueScheduleData.put(overdueInstallment.getLoanId(), loanData); + } + } - for (final Long loanId : overdueScheduleData.keySet()) { - try { - this.loanWritePlatformService.applyOverdueChargesForLoan(loanId, overdueScheduleData.get(loanId)); + for (final Long loanId : overdueScheduleData.keySet()) { + try { + this.loanWritePlatformService.applyOverdueChargesForLoan(loanId, overdueScheduleData.get(loanId)); - } catch (final PlatformApiDataValidationException e) { - final List errors = e.getErrors(); - for (final ApiParameterError error : errors) { - logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message " - + error.getDeveloperMessage()); - sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ") - .append(error.getDeveloperMessage()); - } - } catch (final AbstractPlatformDomainRuleException ex) { - logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message " - + ex.getDefaultUserMessage()); - sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ") - .append(ex.getDefaultUserMessage()); - } catch (Exception e) { - Throwable realCause = e; - if (e.getCause() != null) { - realCause = e.getCause(); - } - logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message " - + realCause.getMessage()); - sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ") - .append(realCause.getMessage()); - } - } - if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); } - } - } + } catch (final PlatformApiDataValidationException e) { + final List errors = e.getErrors(); + for (final ApiParameterError error : errors) { + logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message " + + error.getDeveloperMessage()); + sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ") + .append(error.getDeveloperMessage()); + } + } catch (final AbstractPlatformDomainRuleException ex) { + logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message " + + ex.getDefaultUserMessage()); + sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ") + .append(ex.getDefaultUserMessage()); + } catch (Exception e) { + Throwable realCause = e; + if (e.getCause() != null) { + realCause = e.getCause(); + } + logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message " + + realCause.getMessage()); + sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ") + .append(realCause.getMessage()); + } + } + if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); } + } + } @Override @CronTarget(jobName = JobName.RECALCULATE_INTEREST_FOR_LOAN) @@ -170,7 +177,7 @@ public class LoanSchedularServiceImpl implements LoanSchedularService { logger.error("Interest recalculation for loans failed for account:" + loanId + " with message " + realCause.getMessage()); sb.append("Interest recalculation for loans failed for account:").append(loanId).append(" with message ") - .append(realCause.getMessage()); + .append(realCause.getMessage()); numberOfRetries = maxNumberOfRetries + 1; } i++; @@ -184,4 +191,134 @@ public class LoanSchedularServiceImpl implements LoanSchedularService { } -} + @Override + @CronTarget(jobName = JobName.RECALCULATE_INTEREST_FOR_LOAN) + public void recalculateInterest(Map jobParameters) { + //gets the officeId + final String officeId = jobParameters.get("officeId"); + logger.info(officeId); + Long officeIdLong=Long.valueOf(officeId); + //gets the Office object + final OfficeData office = this.officeReadPlatformService.retrieveOffice(officeIdLong); + if(office == null) + throw new OfficeNotFoundException(officeIdLong); + final int threadPoolSize=Integer.parseInt(jobParameters.get("thread-pool-size")); + final int batchSize=Integer.parseInt(jobParameters.get("batch-size")); + + recalculateInterest(office,threadPoolSize,batchSize); + } + + @Override + public void recalculateInterest(OfficeData office, int threadPoolSize, int batchSize) { + final int pageSize = batchSize * threadPoolSize; + + //initialise the executor service with fetched configurations + final ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize); + + Long maxLoanIdInList = 0L; + final String officeHierarchy = office.getHierarchy() + "%"; + + //Get the loanIds from service + List loanIds = Collections.synchronizedList(this.loanReadPlatformService + .fetchLoansForInterestRecalculation(pageSize, maxLoanIdInList, officeHierarchy)); + + + // gets the loanIds data set iteratively and call addAccuruals for that paginated dataset + do { + int totalFilteredRecords = loanIds.size(); + logger.info("Starting accrual - total filtered records - " + totalFilteredRecords); + recalculateInterest(loanIds, threadPoolSize, batchSize, + executorService); + maxLoanIdInList+= pageSize+1; + loanIds = Collections.synchronizedList(this.loanReadPlatformService + .fetchLoansForInterestRecalculation(pageSize, maxLoanIdInList, officeHierarchy)); + } while (!CollectionUtils.isEmpty(loanIds)); + + //shutdown the executor when done + executorService.shutdownNow(); + } + + private void recalculateInterest(List loanIds, + int threadPoolSize, int batchSize, final ExecutorService executorService) { + StringBuilder sb = new StringBuilder(); + + List> posters = new ArrayList>(); + int fromIndex = 0; + // get the size of current paginated dataset + int size = loanIds.size(); + //calculate the batch size + batchSize = (int) Math.ceil(size / threadPoolSize); + + if(batchSize == 0) + return; + + int toIndex = (batchSize > size - 1)? size : batchSize ; + while(toIndex < size && loanIds.get(toIndex - 1).equals(loanIds.get(toIndex))) { + toIndex ++; + } + boolean lastBatch = false; + int loopCount = size/batchSize+1; + + for (long i=0; i < loopCount; i++) { + List subList = safeSubList(loanIds, fromIndex, toIndex); + RecalculateInterestPoster poster = (RecalculateInterestPoster) this.applicationContext.getBean("recalculateInterestPoster"); + poster.setLoanIds(subList); + poster.setLoanWritePlatformService(loanWritePlatformService); + posters.add(Executors.callable(poster)); + if(lastBatch) + break; + if(toIndex + batchSize > size - 1) + lastBatch = true; + fromIndex = fromIndex + (toIndex - fromIndex); + toIndex = (toIndex + batchSize > size - 1)? size : toIndex + batchSize; + while(toIndex < size && loanIds.get(toIndex - 1).equals(loanIds.get(toIndex))) { + toIndex ++; + } + } + + try { + List> responses = executorService.invokeAll(posters); + checkCompletion(responses); + } catch (InterruptedException e1) { + logger.error("Interrupted while recalculateInterest", e1); + } + } + //break the lists into sub lists + public List safeSubList(List list, int fromIndex, int toIndex) { + int size = list.size(); + if (fromIndex >= size || toIndex <= 0 || fromIndex >= toIndex) { + return Collections.emptyList(); + } + + fromIndex = Math.max(0, fromIndex); + toIndex = Math.min(size, toIndex); + + return list.subList(fromIndex, toIndex); + } + //checks the execution of task by each thread in the executor service + private void checkCompletion(List> responses) { + try { + for(Future f : responses) { + f.get(); + } + boolean allThreadsExecuted = false; + int noOfThreadsExecuted = 0; + for (Future future : responses) { + if (future.isDone()) { + noOfThreadsExecuted++; + } + } + allThreadsExecuted = noOfThreadsExecuted == responses.size(); + if(!allThreadsExecuted) + logger.error("All threads could not execute."); + } catch (InterruptedException e1) { + logger.error("Interrupted while posting IR entries", e1); + } catch (ExecutionException e2) { + logger.error("Execution exception while posting IR entries", e2); + } + } + + + + +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java new file mode 100644 index 000000000..4a08f9955 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java @@ -0,0 +1,151 @@ +/** + * 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.service; + +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationRepository; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; +import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; +import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData; +import org.joda.time.LocalDate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.dao.CannotAcquireLockException; +import org.springframework.orm.ObjectOptimisticLockingFailureException; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.*; + + +@Component + +@Scope("prototype") + +public class RecalculateInterestPoster implements Runnable { + + + + private final static Logger logger = LoggerFactory.getLogger(" recalculate interest poster"); + + private Collection loanIds; + + private LoanWritePlatformService loanWritePlatformService; + + + public void setLoanIds(final Collection loanIds) { + + this.loanIds = loanIds; + + } + + + + public void setLoanWritePlatformService(final LoanWritePlatformService loanWritePlatformService) { + + this.loanWritePlatformService = loanWritePlatformService; + + } + + + + @Override + public void run() { + + Integer maxNumberOfRetries = ThreadLocalContextUtil.getTenant() + .getConnection().getMaxRetriesOnDeadlock(); + Integer maxIntervalBetweenRetries = ThreadLocalContextUtil.getTenant() + .getConnection().getMaxIntervalBetweenRetries(); + + int i = 0; + if (!loanIds.isEmpty()) { + final StringBuilder sb = new StringBuilder(); + for (Long loanId : loanIds) { + logger.info("Loan ID " + loanId); + Integer numberOfRetries = 0; + while (numberOfRetries <= maxNumberOfRetries) { + try { + this.loanWritePlatformService + .recalculateInterest(loanId); + numberOfRetries = maxNumberOfRetries + 1; + } catch (CannotAcquireLockException + | ObjectOptimisticLockingFailureException exception) { + logger.info("Recalulate interest job has been retried " + + numberOfRetries + " time(s)"); + /*** + * Fail if the transaction has been retired for + * maxNumberOfRetries + **/ + if (numberOfRetries >= maxNumberOfRetries) { + logger.warn("Recalulate interest job has been retried for the max allowed attempts of " + + numberOfRetries + + " and will be rolled back"); + sb.append("Recalulate interest job has been retried for the max allowed attempts of " + + numberOfRetries + + " and will be rolled back"); + break; + } + /*** + * Else sleep for a random time (between 1 to 10 + * seconds) and continue + **/ + try { + Random random = new Random(); + int randomNum = random + .nextInt(maxIntervalBetweenRetries + 1); + Thread.sleep(1000 + (randomNum * 1000)); + numberOfRetries = numberOfRetries + 1; + } catch (InterruptedException e) { + sb.append("Interest recalculation for loans failed " + exception.getMessage()) ; + break; + } + } catch (Exception e) { + Throwable realCause = e; + if (e.getCause() != null) { + realCause = e.getCause(); + } + logger.error("Interest recalculation for loans failed for account:" + loanId + " with message " + realCause.getMessage(), e); + sb.append("Interest recalculation for loans failed for account:").append(loanId).append(" with message ") + .append(realCause.getMessage()); + numberOfRetries = maxNumberOfRetries + 1; + } + i++; + } + logger.info("Loans count " + i); + } + if (sb.length() > 0) { + try { + throw new JobExecutionException(sb.toString()); + } catch (JobExecutionException e) { + logger.info("JobExecutionException occured :", e); + } + } + } + + + } + + + +} \ No newline at end of file diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V344__parallelizing_and_paging_recalculate_interest_for_loans_job.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V344__parallelizing_and_paging_recalculate_interest_for_loans_job.sql new file mode 100644 index 000000000..5525fe293 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V344__parallelizing_and_paging_recalculate_interest_for_loans_job.sql @@ -0,0 +1,33 @@ +-- +-- 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. +-- + +CREATE TABLE IF NOT EXISTS `job_parameters` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `job_id` BIGINT(20) NOT NULL, + `parameter_name` VARCHAR(100) NOT NULL, + `parameter_value` INT(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_job_id` (`job_id`), + CONSTRAINT `FK_job_id` FOREIGN KEY (`job_id`) REFERENCES `job` (`id`) + +); + +INSERT IGNORE INTO `job_parameters`(`job_id`,`parameter_name`,`parameter_value`) VALUES(17,'thread-pool-size',10); +INSERT IGNORE INTO `job_parameters`(`job_id`,`parameter_name`,`parameter_value`) VALUES(17,'batch-size',100); +INSERT IGNORE INTO `job_parameters`(`job_id`,`parameter_name`,`parameter_value`) VALUES(17,'officeId',1); \ No newline at end of file