[FINERACT-1667] Introducing Spring Batch as job runner engine
This commit is contained in:
committed by
Arnold Gálovics
parent
d03044c48b
commit
ff9181cc47
@@ -66,3 +66,10 @@ jobs:
|
|||||||
|
|
||||||
- name: OAuth2 Build & Test
|
- name: OAuth2 Build & Test
|
||||||
run: ./gradlew --no-daemon -q --console=plain :oauth2-tests:test --fail-fast -PdbType=postgresql
|
run: ./gradlew --no-daemon -q --console=plain :oauth2-tests:test --fail-fast -PdbType=postgresql
|
||||||
|
|
||||||
|
- name: Archive test results
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: test-results
|
||||||
|
path: home/runner/work/fineract/fineract/integration-tests/build/reports/
|
||||||
|
|||||||
@@ -129,6 +129,9 @@ dependencies {
|
|||||||
exclude group: 'xml-apis'
|
exclude group: 'xml-apis'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implementation ('org.springframework.boot:spring-boot-starter-batch') {
|
||||||
|
}
|
||||||
|
|
||||||
runtimeOnly('org.glassfish.jaxb:jaxb-runtime') {
|
runtimeOnly('org.glassfish.jaxb:jaxb-runtime') {
|
||||||
exclude group: 'com.sun.activation'
|
exclude group: 'com.sun.activation'
|
||||||
}
|
}
|
||||||
|
|||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* 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.accounting.glaccount.jobs.updatetrialbalancedetails;
|
||||||
|
|
||||||
|
import org.apache.fineract.accounting.glaccount.domain.TrialBalanceRepositoryWrapper;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.RoutingDataSourceServiceFactory;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class UpdateTrialBalanceDetailsConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private RoutingDataSourceServiceFactory dataSourceServiceFactory;
|
||||||
|
@Autowired
|
||||||
|
private TrialBalanceRepositoryWrapper trialBalanceRepositoryWrapper;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step updateTrialBalanceDetailsStep() {
|
||||||
|
return steps.get(JobName.UPDATE_TRIAL_BALANCE_DETAILS.name()).tasklet(updateTrialBalanceDetailsTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job updateTrialBalanceDetailsJob() {
|
||||||
|
return jobs.get(JobName.UPDATE_TRIAL_BALANCE_DETAILS.name()).start(updateTrialBalanceDetailsStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UpdateTrialBalanceDetailsTasklet updateTrialBalanceDetailsTasklet() {
|
||||||
|
return new UpdateTrialBalanceDetailsTasklet(dataSourceServiceFactory, trialBalanceRepositoryWrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* 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.accounting.glaccount.jobs.updatetrialbalancedetails;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.accounting.glaccount.domain.TrialBalance;
|
||||||
|
import org.apache.fineract.accounting.glaccount.domain.TrialBalanceRepositoryWrapper;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.RoutingDataSourceServiceFactory;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpdateTrialBalanceDetailsTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final RoutingDataSourceServiceFactory dataSourceServiceFactory;
|
||||||
|
private final TrialBalanceRepositoryWrapper trialBalanceRepositoryWrapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
|
||||||
|
final StringBuilder tbGapSqlBuilder = new StringBuilder(500);
|
||||||
|
tbGapSqlBuilder.append("select distinct(je.transaction_date) ").append("from acc_gl_journal_entry je ")
|
||||||
|
.append("where je.transaction_date > (select coalesce(MAX(created_date),'2010-01-01') from m_trial_balance)");
|
||||||
|
final List<LocalDate> tbGaps = jdbcTemplate.queryForList(tbGapSqlBuilder.toString(), LocalDate.class);
|
||||||
|
for (LocalDate tbGap : tbGaps) {
|
||||||
|
int days = Math.toIntExact(ChronoUnit.DAYS.between(tbGap, DateUtils.getBusinessLocalDate()));
|
||||||
|
if (days < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final StringBuilder sqlBuilder = new StringBuilder(600);
|
||||||
|
sqlBuilder.append("Insert Into m_trial_balance(office_id, account_id, Amount, entry_date, created_date,closing_balance) ")
|
||||||
|
.append("Select je.office_id, je.account_id, SUM(CASE WHEN je.type_enum=1 THEN (-1) * je.amount ELSE je.amount END) ")
|
||||||
|
.append("as Amount, Date(je.entry_date) as Entry_Date, je.transaction_date as Created_Date,sum(je.amount) as closing_balance ")
|
||||||
|
.append("from acc_gl_journal_entry je WHERE je.transaction_date = ? ")
|
||||||
|
.append("group by je.account_id, je.office_id, je.transaction_date, Date(je.entry_date)");
|
||||||
|
final int result = jdbcTemplate.update(sqlBuilder.toString(), tbGap);
|
||||||
|
log.info("{}: Records affected by updateTrialBalanceDetails: {}", ThreadLocalContextUtil.getTenant().getName(), result);
|
||||||
|
}
|
||||||
|
String distinctOfficeQuery = "select distinct(office_id) from m_trial_balance where closing_balance is null group by office_id";
|
||||||
|
final List<Long> officeIds = jdbcTemplate.queryForList(distinctOfficeQuery, Long.class);
|
||||||
|
for (Long officeId : officeIds) {
|
||||||
|
String distinctAccountQuery = "select distinct(account_id) from m_trial_balance where office_id=? and closing_balance is null group by account_id";
|
||||||
|
final List<Long> accountIds = jdbcTemplate.queryForList(distinctAccountQuery, Long.class, officeId);
|
||||||
|
for (Long accountId : accountIds) {
|
||||||
|
final String closingBalanceQuery = "select closing_balance from m_trial_balance where office_id=? and account_id=? and closing_balance "
|
||||||
|
+ "is not null order by created_date desc, entry_date desc limit 1";
|
||||||
|
List<BigDecimal> closingBalanceData = jdbcTemplate.queryForList(closingBalanceQuery, BigDecimal.class, officeId, accountId);
|
||||||
|
List<TrialBalance> tbRows = trialBalanceRepositoryWrapper.findNewByOfficeAndAccount(officeId, accountId);
|
||||||
|
BigDecimal closingBalance = null;
|
||||||
|
if (!CollectionUtils.isEmpty(closingBalanceData)) {
|
||||||
|
closingBalance = closingBalanceData.get(0);
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isEmpty(closingBalanceData)) {
|
||||||
|
closingBalance = BigDecimal.ZERO;
|
||||||
|
for (TrialBalance row : tbRows) {
|
||||||
|
closingBalance = closingBalance.add(row.getAmount());
|
||||||
|
row.setClosingBalance(closingBalance);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (TrialBalance tbRow : tbRows) {
|
||||||
|
if (closingBalance != null) {
|
||||||
|
closingBalance = closingBalance.add(tbRow.getAmount());
|
||||||
|
}
|
||||||
|
tbRow.setClosingBalance(closingBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trialBalanceRepositoryWrapper.save(tbRows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* 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.accounting.jobs.accountrunningbalanceupdate;
|
||||||
|
|
||||||
|
import org.apache.fineract.accounting.journalentry.service.JournalEntryRunningBalanceUpdateService;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class AccountRunningBalanceUpdateConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private JournalEntryRunningBalanceUpdateService journalEntryRunningBalanceUpdateService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step accountRunningBalanceUpdateStep() {
|
||||||
|
return steps.get(JobName.ACCOUNTING_RUNNING_BALANCE_UPDATE.name()).tasklet(accountRunningBalanceUpdateTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job accountRunningBalanceUpdateJob() {
|
||||||
|
return jobs.get(JobName.ACCOUNTING_RUNNING_BALANCE_UPDATE.name()).start(accountRunningBalanceUpdateStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AccountRunningBalanceUpdateTasklet accountRunningBalanceUpdateTasklet() {
|
||||||
|
return new AccountRunningBalanceUpdateTasklet(journalEntryRunningBalanceUpdateService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* 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.accounting.jobs.accountrunningbalanceupdate;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.accounting.journalentry.service.JournalEntryRunningBalanceUpdateService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AccountRunningBalanceUpdateTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final JournalEntryRunningBalanceUpdateService journalEntryRunningBalanceUpdateService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
journalEntryRunningBalanceUpdateService.updateRunningBalance();
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
-3
@@ -41,8 +41,6 @@ import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
|||||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
|
|
||||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
|
||||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||||
import org.springframework.dao.EmptyResultDataAccessException;
|
import org.springframework.dao.EmptyResultDataAccessException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
@@ -66,7 +64,6 @@ public class JournalEntryRunningBalanceUpdateServiceImpl implements JournalEntry
|
|||||||
private final GLJournalEntryMapper entryMapper = new GLJournalEntryMapper();
|
private final GLJournalEntryMapper entryMapper = new GLJournalEntryMapper();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CronTarget(jobName = JobName.ACCOUNTING_RUNNING_BALANCE_UPDATE)
|
|
||||||
public void updateRunningBalance() {
|
public void updateRunningBalance() {
|
||||||
String dateFinder = "select MIN(je.entry_date) as entityDate from acc_gl_journal_entry je "
|
String dateFinder = "select MIN(je.entry_date) as entityDate from acc_gl_journal_entry je "
|
||||||
+ "where je.is_running_balance_calculated=false ";
|
+ "where je.is_running_balance_calculated=false ";
|
||||||
|
|||||||
+4
-2
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.accounting.provisioning.service;
|
package org.apache.fineract.accounting.provisioning.service;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import org.apache.fineract.accounting.provisioning.domain.ProvisioningEntry;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
|
||||||
@@ -25,9 +27,9 @@ public interface ProvisioningEntriesWritePlatformService {
|
|||||||
|
|
||||||
CommandProcessingResult createProvisioningEntries(JsonCommand command);
|
CommandProcessingResult createProvisioningEntries(JsonCommand command);
|
||||||
|
|
||||||
|
ProvisioningEntry createProvisioningEntry(LocalDate date, boolean addJournalEntries);
|
||||||
|
|
||||||
CommandProcessingResult reCreateProvisioningEntries(Long provisioningEntryId, JsonCommand command);
|
CommandProcessingResult reCreateProvisioningEntries(Long provisioningEntryId, JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult createProvisioningJournalEntries(Long provisioningEntryId, JsonCommand command);
|
CommandProcessingResult createProvisioningJournalEntries(Long provisioningEntryId, JsonCommand command);
|
||||||
|
|
||||||
void generateLoanLossProvisioningAmount();
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-33
@@ -23,7 +23,6 @@ import java.time.LocalDate;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
|
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
|
||||||
@@ -44,9 +43,6 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
|
||||||
import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
|
|
||||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
@@ -129,27 +125,6 @@ public class ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl implements
|
|||||||
return this.fromApiJsonHelper.extractLocalDateNamed("date", command.parsedJson());
|
return this.fromApiJsonHelper.extractLocalDateNamed("date", command.parsedJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.GENERATE_LOANLOSS_PROVISIONING)
|
|
||||||
public void generateLoanLossProvisioningAmount() {
|
|
||||||
LocalDate currentDate = DateUtils.getBusinessLocalDate();
|
|
||||||
boolean addJournalEntries = true;
|
|
||||||
try {
|
|
||||||
Collection<ProvisioningCriteriaData> criteriaCollection = this.provisioningCriteriaReadPlatformService
|
|
||||||
.retrieveAllProvisioningCriterias();
|
|
||||||
if (criteriaCollection == null || criteriaCollection.size() == 0) {
|
|
||||||
return;
|
|
||||||
// FIXME: Do we need to throw
|
|
||||||
// NoProvisioningCriteriaDefinitionFound()?
|
|
||||||
}
|
|
||||||
createProvsioningEntry(currentDate, addJournalEntries);
|
|
||||||
} catch (ProvisioningEntryAlreadyCreatedException peace) {
|
|
||||||
log.error("Provisioning Entry already created", peace);
|
|
||||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
|
||||||
log.error("Problem occurred in generateLoanLossProvisioningAmount function", dve);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult createProvisioningEntries(JsonCommand command) {
|
public CommandProcessingResult createProvisioningEntries(JsonCommand command) {
|
||||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
||||||
@@ -161,29 +136,27 @@ public class ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl implements
|
|||||||
if (criteriaCollection == null || criteriaCollection.size() == 0) {
|
if (criteriaCollection == null || criteriaCollection.size() == 0) {
|
||||||
throw new NoProvisioningCriteriaDefinitionFound();
|
throw new NoProvisioningCriteriaDefinitionFound();
|
||||||
}
|
}
|
||||||
ProvisioningEntry requestedEntry = createProvsioningEntry(createdDate, addJournalEntries);
|
ProvisioningEntry requestedEntry = createProvisioningEntry(createdDate, addJournalEntries);
|
||||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(requestedEntry.getId()).build();
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(requestedEntry.getId()).build();
|
||||||
} catch (final JpaSystemException | DataIntegrityViolationException e) {
|
} catch (final JpaSystemException | DataIntegrityViolationException e) {
|
||||||
return CommandProcessingResult.empty();
|
return CommandProcessingResult.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProvisioningEntry createProvsioningEntry(LocalDate date, boolean addJournalEntries) {
|
@Override
|
||||||
|
public ProvisioningEntry createProvisioningEntry(LocalDate date, boolean addJournalEntries) {
|
||||||
ProvisioningEntry existingEntry = this.provisioningEntryRepository.findByProvisioningEntryDate(date);
|
ProvisioningEntry existingEntry = this.provisioningEntryRepository.findByProvisioningEntryDate(date);
|
||||||
if (existingEntry != null) {
|
if (existingEntry != null) {
|
||||||
throw new ProvisioningEntryAlreadyCreatedException(existingEntry.getId(), existingEntry.getCreatedDate());
|
throw new ProvisioningEntryAlreadyCreatedException(existingEntry.getId(), existingEntry.getCreatedDate());
|
||||||
}
|
}
|
||||||
AppUser currentUser = this.platformSecurityContext.authenticatedUser();
|
AppUser currentUser = this.platformSecurityContext.authenticatedUser();
|
||||||
AppUser lastModifiedBy = null;
|
ProvisioningEntry requestedEntry = new ProvisioningEntry(currentUser, date, null, null, null);
|
||||||
LocalDate lastModifiedDate = null;
|
|
||||||
Set<LoanProductProvisioningEntry> nullEntries = null;
|
|
||||||
ProvisioningEntry requestedEntry = new ProvisioningEntry(currentUser, date, lastModifiedBy, lastModifiedDate, nullEntries);
|
|
||||||
Collection<LoanProductProvisioningEntry> entries = generateLoanProvisioningEntry(requestedEntry, date);
|
Collection<LoanProductProvisioningEntry> entries = generateLoanProvisioningEntry(requestedEntry, date);
|
||||||
requestedEntry.setProvisioningEntries(entries);
|
requestedEntry.setProvisioningEntries(entries);
|
||||||
if (addJournalEntries) {
|
if (addJournalEntries) {
|
||||||
ProvisioningEntryData exisProvisioningEntryData = this.provisioningEntriesReadPlatformService
|
ProvisioningEntryData existingProvisioningEntryData = this.provisioningEntriesReadPlatformService
|
||||||
.retrieveExistingProvisioningIdDateWithJournals();
|
.retrieveExistingProvisioningIdDateWithJournals();
|
||||||
revertAndAddJournalEntries(exisProvisioningEntryData, requestedEntry);
|
revertAndAddJournalEntries(existingProvisioningEntryData, requestedEntry);
|
||||||
} else {
|
} else {
|
||||||
this.provisioningEntryRepository.saveAndFlush(requestedEntry);
|
this.provisioningEntryRepository.saveAndFlush(requestedEntry);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-23
@@ -35,6 +35,7 @@ import javax.ws.rs.Produces;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.adhocquery.data.AdHocData;
|
import org.apache.fineract.adhocquery.data.AdHocData;
|
||||||
import org.apache.fineract.adhocquery.service.AdHocReadPlatformService;
|
import org.apache.fineract.adhocquery.service.AdHocReadPlatformService;
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
@@ -45,7 +46,6 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|||||||
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -53,6 +53,7 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
@Scope("singleton")
|
@Scope("singleton")
|
||||||
@Tag(name = "AdhocQuery Api", description = "")
|
@Tag(name = "AdhocQuery Api", description = "")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class AdHocApiResource {
|
public class AdHocApiResource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,17 +68,6 @@ public class AdHocApiResource {
|
|||||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public AdHocApiResource(final PlatformSecurityContext context, final AdHocReadPlatformService readPlatformService,
|
|
||||||
final DefaultToApiJsonSerializer<AdHocData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
|
|
||||||
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
|
|
||||||
this.context = context;
|
|
||||||
this.adHocReadPlatformService = readPlatformService;
|
|
||||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
|
||||||
this.apiRequestParameterHelper = apiRequestParameterHelper;
|
|
||||||
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
@@ -105,10 +95,7 @@ public class AdHocApiResource {
|
|||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
public String createAdHocQuery(final String apiRequestBodyAsJson) {
|
public String createAdHocQuery(final String apiRequestBodyAsJson) {
|
||||||
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().createAdHoc().withJson(apiRequestBodyAsJson).build();
|
||||||
.createAdHoc() //
|
|
||||||
.withJson(apiRequestBodyAsJson) //
|
|
||||||
.build();
|
|
||||||
|
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
|
||||||
@@ -137,10 +124,7 @@ public class AdHocApiResource {
|
|||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
public String update(@PathParam("adHocId") @Parameter(description = "adHocId") final Long adHocId, final String apiRequestBodyAsJson) {
|
public String update(@PathParam("adHocId") @Parameter(description = "adHocId") final Long adHocId, final String apiRequestBodyAsJson) {
|
||||||
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateAdHoc(adHocId).withJson(apiRequestBodyAsJson).build();
|
||||||
.updateAdHoc(adHocId) //
|
|
||||||
.withJson(apiRequestBodyAsJson) //
|
|
||||||
.build();
|
|
||||||
|
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
|
||||||
@@ -159,9 +143,7 @@ public class AdHocApiResource {
|
|||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
public String deleteAdHocQuery(@PathParam("adHocId") @Parameter(description = "adHocId") final Long adHocId) {
|
public String deleteAdHocQuery(@PathParam("adHocId") @Parameter(description = "adHocId") final Long adHocId) {
|
||||||
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteAdHoc(adHocId).build();
|
||||||
.deleteAdHoc(adHocId) //
|
|
||||||
.build();
|
|
||||||
|
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
|
||||||
|
|||||||
+2
-5
@@ -55,18 +55,15 @@ public class AdHocReadPlatformServiceImpl implements AdHocReadPlatformService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<AdHocData> retrieveAllActiveAdHocQuery() {
|
public Collection<AdHocData> retrieveAllActiveAdHocQuery() {
|
||||||
final String sql = "select " + this.adHocRowMapper.schema() + " where r." + sqlGenerator.escape("is_active")
|
final String sql = "select " + adHocRowMapper.schema() + " where r." + sqlGenerator.escape("is_active") + " = true order by r.id";
|
||||||
+ " = true order by r.id";
|
|
||||||
|
|
||||||
return this.jdbcTemplate.query(sql, this.adHocRowMapper); // NOSONAR
|
return jdbcTemplate.query(sql, adHocRowMapper); // NOSONAR
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AdHocData retrieveOne(final Long id) {
|
public AdHocData retrieveOne(final Long id) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final String sql = "select " + this.adHocRowMapper.schema() + " where r.id=?";
|
final String sql = "select " + this.adHocRowMapper.schema() + " where r.id=?";
|
||||||
|
|
||||||
return this.jdbcTemplate.queryForObject(sql, this.adHocRowMapper, new Object[] { id }); // NOSONAR
|
return this.jdbcTemplate.queryForObject(sql, this.adHocRowMapper, new Object[] { id }); // NOSONAR
|
||||||
} catch (final EmptyResultDataAccessException e) {
|
} catch (final EmptyResultDataAccessException e) {
|
||||||
throw new AdHocNotFoundException(id, e);
|
throw new AdHocNotFoundException(id, e);
|
||||||
|
|||||||
+8
-25
@@ -19,6 +19,8 @@
|
|||||||
package org.apache.fineract.adhocquery.service;
|
package org.apache.fineract.adhocquery.service;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.adhocquery.domain.AdHoc;
|
import org.apache.fineract.adhocquery.domain.AdHoc;
|
||||||
import org.apache.fineract.adhocquery.domain.AdHocRepository;
|
import org.apache.fineract.adhocquery.domain.AdHocRepository;
|
||||||
import org.apache.fineract.adhocquery.exception.AdHocNotFoundException;
|
import org.apache.fineract.adhocquery.exception.AdHocNotFoundException;
|
||||||
@@ -27,9 +29,6 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.dao.NonTransientDataAccessException;
|
import org.springframework.dao.NonTransientDataAccessException;
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
@@ -37,22 +36,14 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePlatformService {
|
public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePlatformService {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AdHocWritePlatformServiceJpaRepositoryImpl.class);
|
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
private final AdHocRepository adHocRepository;
|
private final AdHocRepository adHocRepository;
|
||||||
private final AdHocDataValidator adHocCommandFromApiJsonDeserializer;
|
private final AdHocDataValidator adHocCommandFromApiJsonDeserializer;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public AdHocWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final AdHocRepository adHocRepository,
|
|
||||||
final AdHocDataValidator adHocCommandFromApiJsonDeserializer) {
|
|
||||||
this.context = context;
|
|
||||||
this.adHocRepository = adHocRepository;
|
|
||||||
this.adHocCommandFromApiJsonDeserializer = adHocCommandFromApiJsonDeserializer;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult createAdHocQuery(final JsonCommand command) {
|
public CommandProcessingResult createAdHocQuery(final JsonCommand command) {
|
||||||
@@ -69,9 +60,7 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
|
|||||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||||
final Throwable throwable = dve.getMostSpecificCause();
|
final Throwable throwable = dve.getMostSpecificCause();
|
||||||
handleDataIntegrityIssues(command, throwable, dve);
|
handleDataIntegrityIssues(command, throwable, dve);
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).build();
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +77,7 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
|
|||||||
"AdHocQuery with name `" + name + "` already exists", "name", name);
|
"AdHocQuery with name `" + name + "` already exists", "name", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.error("Error occured.", dve);
|
log.error("Error occured.", dve);
|
||||||
throw new PlatformDataIntegrityException("error.msg.adhocquery.unknown.data.integrity.issue",
|
throw new PlatformDataIntegrityException("error.msg.adhocquery.unknown.data.integrity.issue",
|
||||||
"Unknown data integrity issue with resource.");
|
"Unknown data integrity issue with resource.");
|
||||||
}
|
}
|
||||||
@@ -108,17 +97,11 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
|
|||||||
this.adHocRepository.saveAndFlush(adHoc);
|
this.adHocRepository.saveAndFlush(adHoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(adHocId).with(changes).build();
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.withEntityId(adHocId) //
|
|
||||||
.with(changes) //
|
|
||||||
.build();
|
|
||||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||||
final Throwable throwable = dve.getMostSpecificCause();
|
final Throwable throwable = dve.getMostSpecificCause();
|
||||||
handleDataIntegrityIssues(command, throwable, dve);
|
handleDataIntegrityIssues(command, throwable, dve);
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).build();
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.businessdate.service;
|
package org.apache.fineract.infrastructure.businessdate.service;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateData;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||||
@@ -26,6 +28,8 @@ public interface BusinessDateWritePlatformService {
|
|||||||
|
|
||||||
CommandProcessingResult updateBusinessDate(JsonCommand command);
|
CommandProcessingResult updateBusinessDate(JsonCommand command);
|
||||||
|
|
||||||
|
void adjustDate(BusinessDateData data, Map<String, Object> changes);
|
||||||
|
|
||||||
void increaseCOBDateByOneDay() throws JobExecutionException;
|
void increaseCOBDateByOneDay() throws JobExecutionException;
|
||||||
|
|
||||||
void increaseBusinessDateByOneDay() throws JobExecutionException;
|
void increaseBusinessDateByOneDay() throws JobExecutionException;
|
||||||
|
|||||||
+3
-7
@@ -42,9 +42,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
|
|||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
|
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -65,7 +63,8 @@ public class BusinessDateWritePlatformServiceImpl implements BusinessDateWritePl
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void adjustDate(BusinessDateData data, Map<String, Object> changes) {
|
@Override
|
||||||
|
public void adjustDate(BusinessDateData data, Map<String, Object> changes) {
|
||||||
boolean isCOBDateAdjustmentEnabled = configurationDomainService.isCOBDateAdjustmentEnabled();
|
boolean isCOBDateAdjustmentEnabled = configurationDomainService.isCOBDateAdjustmentEnabled();
|
||||||
boolean isBusinessDateEnabled = configurationDomainService.isBusinessDateEnabled();
|
boolean isBusinessDateEnabled = configurationDomainService.isBusinessDateEnabled();
|
||||||
|
|
||||||
@@ -80,21 +79,19 @@ public class BusinessDateWritePlatformServiceImpl implements BusinessDateWritePl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CronTarget(jobName = JobName.INCREASE_COB_DATE_BY_1_DAY)
|
|
||||||
public void increaseCOBDateByOneDay() throws JobExecutionException {
|
public void increaseCOBDateByOneDay() throws JobExecutionException {
|
||||||
increaseDateByTypeByOneDay(BusinessDateType.COB_DATE);
|
increaseDateByTypeByOneDay(BusinessDateType.COB_DATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CronTarget(jobName = JobName.INCREASE_BUSINESS_DATE_BY_1_DAY)
|
|
||||||
public void increaseBusinessDateByOneDay() throws JobExecutionException {
|
public void increaseBusinessDateByOneDay() throws JobExecutionException {
|
||||||
increaseDateByTypeByOneDay(BusinessDateType.BUSINESS_DATE);
|
increaseDateByTypeByOneDay(BusinessDateType.BUSINESS_DATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void increaseDateByTypeByOneDay(BusinessDateType businessDateType) throws JobExecutionException {
|
private void increaseDateByTypeByOneDay(BusinessDateType businessDateType) throws JobExecutionException {
|
||||||
List<Throwable> exceptions = new ArrayList<>();
|
|
||||||
Map<String, Object> changes = new HashMap<>();
|
Map<String, Object> changes = new HashMap<>();
|
||||||
Optional<BusinessDate> businessDateEntity = repository.findByType(businessDateType);
|
Optional<BusinessDate> businessDateEntity = repository.findByType(businessDateType);
|
||||||
|
List<Throwable> exceptions = new ArrayList<>();
|
||||||
|
|
||||||
LocalDate businessDate = businessDateEntity.map(BusinessDate::getDate).orElse(DateUtils.getLocalDateOfTenant());
|
LocalDate businessDate = businessDateEntity.map(BusinessDate::getDate).orElse(DateUtils.getLocalDateOfTenant());
|
||||||
businessDate = businessDate.plusDays(1);
|
businessDate = businessDate.plusDays(1);
|
||||||
@@ -114,7 +111,6 @@ public class BusinessDateWritePlatformServiceImpl implements BusinessDateWritePl
|
|||||||
log.error("Increasing {} by 1 day failed due to: {}", businessDateType.getDescription(), e.getMessage());
|
log.error("Increasing {} by 1 day failed due to: {}", businessDateType.getDescription(), e.getMessage());
|
||||||
exceptions.add(e);
|
exceptions.add(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exceptions.isEmpty()) {
|
if (!exceptions.isEmpty()) {
|
||||||
throw new JobExecutionException(exceptions);
|
throw new JobExecutionException(exceptions);
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-42
@@ -32,6 +32,7 @@ import javax.ws.rs.QueryParam;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.accounting.journalentry.api.DateParam;
|
import org.apache.fineract.accounting.journalentry.api.DateParam;
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||||
@@ -45,7 +46,6 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
|
|||||||
import org.apache.fineract.infrastructure.core.service.Page;
|
import org.apache.fineract.infrastructure.core.service.Page;
|
||||||
import org.apache.fineract.infrastructure.core.service.SearchParameters;
|
import org.apache.fineract.infrastructure.core.service.SearchParameters;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -54,32 +54,22 @@ import org.springframework.stereotype.Component;
|
|||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
@Component
|
@Component
|
||||||
@Scope("singleton")
|
@Scope("singleton")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class EmailApiResource {
|
public class EmailApiResource {
|
||||||
|
|
||||||
private final String resourceNameForPermissions = "Email";
|
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "Email";
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
private final EmailReadPlatformService readPlatformService;
|
private final EmailReadPlatformService readPlatformService;
|
||||||
private final DefaultToApiJsonSerializer<EmailData> toApiJsonSerializer;
|
private final DefaultToApiJsonSerializer<EmailData> toApiJsonSerializer;
|
||||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public EmailApiResource(final PlatformSecurityContext context, final EmailReadPlatformService readPlatformService,
|
|
||||||
final DefaultToApiJsonSerializer<EmailData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
|
|
||||||
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
|
|
||||||
this.context = context;
|
|
||||||
this.readPlatformService = readPlatformService;
|
|
||||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
|
||||||
this.apiRequestParameterHelper = apiRequestParameterHelper;
|
|
||||||
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
public String retrieveAllEmails(@Context final UriInfo uriInfo) {
|
public String retrieveAllEmails(@Context final UriInfo uriInfo) {
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
final Collection<EmailData> emailMessages = this.readPlatformService.retrieveAll();
|
final Collection<EmailData> emailMessages = readPlatformService.retrieveAll();
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, emailMessages);
|
return toApiJsonSerializer.serialize(settings, emailMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@@ -88,11 +78,11 @@ public class EmailApiResource {
|
|||||||
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
||||||
@QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
|
@QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
final SearchParameters searchParameters = SearchParameters.forEmailCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
|
final SearchParameters searchParameters = SearchParameters.forEmailCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
|
||||||
Collection<EmailData> emailMessages = this.readPlatformService.retrieveAllPending(searchParameters);
|
Collection<EmailData> emailMessages = readPlatformService.retrieveAllPending(searchParameters);
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, emailMessages);
|
return toApiJsonSerializer.serialize(settings, emailMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@@ -101,13 +91,13 @@ public class EmailApiResource {
|
|||||||
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
||||||
@QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
|
@QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
final SearchParameters searchParameters = SearchParameters.forEmailCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
|
final SearchParameters searchParameters = SearchParameters.forEmailCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
|
||||||
Collection<EmailData> emailMessages = this.readPlatformService.retrieveAllSent(searchParameters);
|
Collection<EmailData> emailMessages = readPlatformService.retrieveAllSent(searchParameters);
|
||||||
|
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, emailMessages);
|
return toApiJsonSerializer.serialize(settings, emailMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@@ -118,7 +108,7 @@ public class EmailApiResource {
|
|||||||
@QueryParam("fromDate") final DateParam fromDateParam, @QueryParam("toDate") final DateParam toDateParam,
|
@QueryParam("fromDate") final DateParam fromDateParam, @QueryParam("toDate") final DateParam toDateParam,
|
||||||
@QueryParam("locale") final String locale, @QueryParam("dateFormat") final String dateFormat, @Context final UriInfo uriInfo) {
|
@QueryParam("locale") final String locale, @QueryParam("dateFormat") final String dateFormat, @Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
LocalDate fromDate = null;
|
LocalDate fromDate = null;
|
||||||
if (fromDateParam != null) {
|
if (fromDateParam != null) {
|
||||||
fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
|
fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
|
||||||
@@ -127,9 +117,9 @@ public class EmailApiResource {
|
|||||||
if (toDateParam != null) {
|
if (toDateParam != null) {
|
||||||
toDate = toDateParam.getDate("toDate", dateFormat, locale);
|
toDate = toDateParam.getDate("toDate", dateFormat, locale);
|
||||||
}
|
}
|
||||||
Page<EmailData> emailMessages = this.readPlatformService.retrieveEmailByStatus(limit, status, fromDate, toDate);
|
Page<EmailData> emailMessages = readPlatformService.retrieveEmailByStatus(limit, status, fromDate, toDate);
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, emailMessages);
|
return toApiJsonSerializer.serialize(settings, emailMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@@ -138,13 +128,13 @@ public class EmailApiResource {
|
|||||||
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
||||||
@QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
|
@QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
final SearchParameters searchParameters = SearchParameters.forEmailCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
|
final SearchParameters searchParameters = SearchParameters.forEmailCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
|
||||||
Collection<EmailData> emailMessages = this.readPlatformService.retrieveAllFailed(searchParameters);
|
Collection<EmailData> emailMessages = readPlatformService.retrieveAllFailed(searchParameters);
|
||||||
|
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, emailMessages);
|
return toApiJsonSerializer.serialize(settings, emailMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -152,19 +142,19 @@ public class EmailApiResource {
|
|||||||
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createEmail().withJson(apiRequestBodyAsJson).build();
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().createEmail().withJson(apiRequestBodyAsJson).build();
|
||||||
|
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{resourceId}")
|
@Path("{resourceId}")
|
||||||
public String retrieveOne(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
|
public String retrieveOne(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
final EmailData emailMessage = this.readPlatformService.retrieveOne(resourceId);
|
final EmailData emailMessage = readPlatformService.retrieveOne(resourceId);
|
||||||
|
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, emailMessage);
|
return toApiJsonSerializer.serialize(settings, emailMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
@@ -173,9 +163,9 @@ public class EmailApiResource {
|
|||||||
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateEmail(resourceId).withJson(apiRequestBodyAsJson).build();
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateEmail(resourceId).withJson(apiRequestBodyAsJson).build();
|
||||||
|
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
@@ -184,8 +174,8 @@ public class EmailApiResource {
|
|||||||
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteEmail(resourceId).build();
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteEmail(resourceId).build();
|
||||||
|
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-57
@@ -33,6 +33,7 @@ import javax.ws.rs.QueryParam;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||||
@@ -49,7 +50,6 @@ import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSeria
|
|||||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -62,10 +62,10 @@ import org.springframework.stereotype.Component;
|
|||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
@Component
|
@Component
|
||||||
@Scope("singleton")
|
@Scope("singleton")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class EmailCampaignApiResource {
|
public class EmailCampaignApiResource {
|
||||||
|
|
||||||
// change name to email campaign
|
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "EMAIL_CAMPAIGN";
|
||||||
private final String resourceNameForPermissions = "EMAIL_CAMPAIGN";
|
|
||||||
|
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
|
|
||||||
@@ -82,35 +82,15 @@ public class EmailCampaignApiResource {
|
|||||||
|
|
||||||
private final DefaultToApiJsonSerializer<PreviewCampaignMessage> previewCampaignMessageDefaultToApiJsonSerializer;
|
private final DefaultToApiJsonSerializer<PreviewCampaignMessage> previewCampaignMessageDefaultToApiJsonSerializer;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public EmailCampaignApiResource(final PlatformSecurityContext context,
|
|
||||||
final DefaultToApiJsonSerializer<EmailBusinessRulesData> toApiJsonSerializer,
|
|
||||||
final ApiRequestParameterHelper apiRequestParameterHelper,
|
|
||||||
final EmailCampaignReadPlatformService emailCampaignReadPlatformService,
|
|
||||||
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
|
|
||||||
final DefaultToApiJsonSerializer<EmailCampaignData> emailCampaignDataDefaultToApiJsonSerializer,
|
|
||||||
final FromJsonHelper fromJsonHelper, final EmailCampaignWritePlatformService emailCampaignWritePlatformService,
|
|
||||||
final DefaultToApiJsonSerializer<PreviewCampaignMessage> previewCampaignMessageDefaultToApiJsonSerializer) {
|
|
||||||
this.context = context;
|
|
||||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
|
||||||
this.apiRequestParameterHelper = apiRequestParameterHelper;
|
|
||||||
this.emailCampaignReadPlatformService = emailCampaignReadPlatformService;
|
|
||||||
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
|
|
||||||
this.emailCampaignDataDefaultToApiJsonSerializer = emailCampaignDataDefaultToApiJsonSerializer;
|
|
||||||
this.fromJsonHelper = fromJsonHelper;
|
|
||||||
this.emailCampaignWritePlatformService = emailCampaignWritePlatformService;
|
|
||||||
this.previewCampaignMessageDefaultToApiJsonSerializer = previewCampaignMessageDefaultToApiJsonSerializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{resourceId}")
|
@Path("{resourceId}")
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
public String retrieveOneCampaign(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
|
public String retrieveOneCampaign(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
EmailCampaignData emailCampaignData = this.emailCampaignReadPlatformService.retrieveOne(resourceId);
|
EmailCampaignData emailCampaignData = emailCampaignReadPlatformService.retrieveOne(resourceId);
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.emailCampaignDataDefaultToApiJsonSerializer.serialize(settings, emailCampaignData);
|
return emailCampaignDataDefaultToApiJsonSerializer.serialize(settings, emailCampaignData);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,12 +98,12 @@ public class EmailCampaignApiResource {
|
|||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
public String retrieveAllCampaign(@Context final UriInfo uriInfo) {
|
public String retrieveAllCampaign(@Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
final Collection<EmailCampaignData> emailCampaignDataCollection = this.emailCampaignReadPlatformService.retrieveAllCampaign();
|
final Collection<EmailCampaignData> emailCampaignDataCollection = emailCampaignReadPlatformService.retrieveAllCampaign();
|
||||||
|
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.emailCampaignDataDefaultToApiJsonSerializer.serialize(settings, emailCampaignDataCollection);
|
return emailCampaignDataDefaultToApiJsonSerializer.serialize(settings, emailCampaignDataCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -133,9 +113,9 @@ public class EmailCampaignApiResource {
|
|||||||
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createEmailCampaign().withJson(apiRequestBodyAsJson).build();
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().createEmailCampaign().withJson(apiRequestBodyAsJson).build();
|
||||||
|
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
@@ -148,9 +128,9 @@ public class EmailCampaignApiResource {
|
|||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateEmailCampaign(campaignId).withJson(apiRequestBodyAsJson)
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateEmailCampaign(campaignId).withJson(apiRequestBodyAsJson)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -162,18 +142,18 @@ public class EmailCampaignApiResource {
|
|||||||
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
|
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
|
||||||
|
|
||||||
CommandProcessingResult result = null;
|
CommandProcessingResult result = null;
|
||||||
CommandWrapper commandRequest = null;
|
CommandWrapper commandRequest;
|
||||||
if (is(commandParam, "activate")) {
|
if (is(commandParam, "activate")) {
|
||||||
commandRequest = builder.activateEmailCampaign(campaignId).build();
|
commandRequest = builder.activateEmailCampaign(campaignId).build();
|
||||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
} else if (is(commandParam, "close")) {
|
} else if (is(commandParam, "close")) {
|
||||||
commandRequest = builder.closeEmailCampaign(campaignId).build();
|
commandRequest = builder.closeEmailCampaign(campaignId).build();
|
||||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
} else if (is(commandParam, "reactivate")) {
|
} else if (is(commandParam, "reactivate")) {
|
||||||
commandRequest = builder.reactivateEmailCampaign(campaignId).build();
|
commandRequest = builder.reactivateEmailCampaign(campaignId).build();
|
||||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
}
|
}
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -181,36 +161,36 @@ public class EmailCampaignApiResource {
|
|||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
public String preview(final String apiRequestBodyAsJson, @Context final UriInfo uriInfo) {
|
public String preview(final String apiRequestBodyAsJson, @Context final UriInfo uriInfo) {
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
PreviewCampaignMessage campaignMessage = null;
|
PreviewCampaignMessage campaignMessage;
|
||||||
final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
|
final JsonElement parsedQuery = fromJsonHelper.parse(apiRequestBodyAsJson);
|
||||||
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
|
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, fromJsonHelper);
|
||||||
campaignMessage = this.emailCampaignWritePlatformService.previewMessage(query);
|
campaignMessage = emailCampaignWritePlatformService.previewMessage(query);
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.previewCampaignMessageDefaultToApiJsonSerializer.serialize(settings, campaignMessage, new HashSet<String>());
|
return previewCampaignMessageDefaultToApiJsonSerializer.serialize(settings, campaignMessage, new HashSet<>());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET()
|
@GET()
|
||||||
@Path("template")
|
@Path("template")
|
||||||
public String template(@Context final UriInfo uriInfo) {
|
public String template(@Context final UriInfo uriInfo) {
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
final Collection<EmailBusinessRulesData> emailBusinessRulesDataCollection = this.emailCampaignReadPlatformService.retrieveAll();
|
final Collection<EmailBusinessRulesData> emailBusinessRulesDataCollection = emailCampaignReadPlatformService.retrieveAll();
|
||||||
|
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, emailBusinessRulesDataCollection);
|
return toApiJsonSerializer.serialize(settings, emailBusinessRulesDataCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("template/{resourceId}")
|
@Path("template/{resourceId}")
|
||||||
public String retrieveOneTemplate(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
|
public String retrieveOneTemplate(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
final EmailBusinessRulesData emailBusinessRulesData = this.emailCampaignReadPlatformService.retrieveOneTemplate(resourceId);
|
final EmailBusinessRulesData emailBusinessRulesData = emailCampaignReadPlatformService.retrieveOneTemplate(resourceId);
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, emailBusinessRulesData);
|
return toApiJsonSerializer.serialize(settings, emailBusinessRulesData);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,9 +200,9 @@ public class EmailCampaignApiResource {
|
|||||||
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteEmailCampaign(resourceId).build();
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteEmailCampaign(resourceId).build();
|
||||||
|
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean is(final String commandParam, final String commandValue) {
|
private boolean is(final String commandParam, final String commandValue) {
|
||||||
|
|||||||
+8
-355
@@ -24,9 +24,6 @@ import com.github.mustachejava.DefaultMustacheFactory;
|
|||||||
import com.github.mustachejava.Mustache;
|
import com.github.mustachejava.Mustache;
|
||||||
import com.github.mustachejava.MustacheFactory;
|
import com.github.mustachejava.MustacheFactory;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
@@ -36,8 +33,6 @@ import java.time.ZoneId;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeFormatterBuilder;
|
import java.time.format.DateTimeFormatterBuilder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -46,17 +41,14 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.mail.internet.AddressException;
|
import javax.mail.internet.AddressException;
|
||||||
import javax.mail.internet.InternetAddress;
|
import javax.mail.internet.InternetAddress;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.data.EmailCampaignData;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.data.EmailCampaignValidator;
|
import org.apache.fineract.infrastructure.campaigns.email.data.EmailCampaignValidator;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.data.EmailMessageWithAttachmentData;
|
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.data.PreviewCampaignMessage;
|
import org.apache.fineract.infrastructure.campaigns.email.data.PreviewCampaignMessage;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaign;
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaign;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignRepository;
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignRepository;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessage;
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessage;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageRepository;
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageRepository;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageStatusType;
|
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.domain.ScheduledEmailAttachmentFileFormat;
|
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignMustBeClosedToBeDeletedException;
|
import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignMustBeClosedToBeDeletedException;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignMustBeClosedToEditException;
|
import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignMustBeClosedToEditException;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignNotFound;
|
import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignNotFound;
|
||||||
@@ -76,23 +68,12 @@ import org.apache.fineract.infrastructure.dataqueries.domain.ReportRepository;
|
|||||||
import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException;
|
import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
|
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||||
import org.apache.fineract.infrastructure.documentmanagement.contentrepository.FileSystemContentRepository;
|
|
||||||
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.infrastructure.reportmailingjob.helper.IPv4Helper;
|
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
|
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.dao.NonTransientDataAccessException;
|
import org.springframework.dao.NonTransientDataAccessException;
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
@@ -100,47 +81,19 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampaignWritePlatformService {
|
public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampaignWritePlatformService {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(EmailCampaignWritePlatformCommandHandlerImpl.class);
|
|
||||||
|
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
|
|
||||||
private final EmailCampaignRepository emailCampaignRepository;
|
private final EmailCampaignRepository emailCampaignRepository;
|
||||||
private final EmailCampaignValidator emailCampaignValidator;
|
private final EmailCampaignValidator emailCampaignValidator;
|
||||||
private final EmailCampaignReadPlatformService emailCampaignReadPlatformService;
|
|
||||||
private final ReportRepository reportRepository;
|
private final ReportRepository reportRepository;
|
||||||
private final EmailMessageRepository emailMessageRepository;
|
private final EmailMessageRepository emailMessageRepository;
|
||||||
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
||||||
private final ReadReportingService readReportingService;
|
private final ReadReportingService readReportingService;
|
||||||
private final GenericDataService genericDataService;
|
private final GenericDataService genericDataService;
|
||||||
private final FromJsonHelper fromJsonHelper;
|
private final FromJsonHelper fromJsonHelper;
|
||||||
private final LoanRepository loanRepository;
|
|
||||||
private final SavingsAccountRepository savingsAccountRepository;
|
|
||||||
private final EmailMessageJobEmailService emailMessageJobEmailService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public EmailCampaignWritePlatformCommandHandlerImpl(final PlatformSecurityContext context,
|
|
||||||
final EmailCampaignRepository emailCampaignRepository, final EmailCampaignValidator emailCampaignValidator,
|
|
||||||
final EmailCampaignReadPlatformService emailCampaignReadPlatformService, final ReportRepository reportRepository,
|
|
||||||
final EmailMessageRepository emailMessageRepository, final ClientRepositoryWrapper clientRepositoryWrapper,
|
|
||||||
final ReadReportingService readReportingService, final GenericDataService genericDataService,
|
|
||||||
final FromJsonHelper fromJsonHelper, final LoanRepository loanRepository,
|
|
||||||
final SavingsAccountRepository savingsAccountRepository, final EmailMessageJobEmailService emailMessageJobEmailService) {
|
|
||||||
this.context = context;
|
|
||||||
this.emailCampaignRepository = emailCampaignRepository;
|
|
||||||
this.emailCampaignValidator = emailCampaignValidator;
|
|
||||||
this.emailCampaignReadPlatformService = emailCampaignReadPlatformService;
|
|
||||||
this.reportRepository = reportRepository;
|
|
||||||
this.emailMessageRepository = emailMessageRepository;
|
|
||||||
this.clientRepositoryWrapper = clientRepositoryWrapper;
|
|
||||||
this.readReportingService = readReportingService;
|
|
||||||
this.genericDataService = genericDataService;
|
|
||||||
this.fromJsonHelper = fromJsonHelper;
|
|
||||||
this.loanRepository = loanRepository;
|
|
||||||
this.savingsAccountRepository = savingsAccountRepository;
|
|
||||||
this.emailMessageJobEmailService = emailMessageJobEmailService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -178,10 +131,7 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai
|
|||||||
|
|
||||||
this.emailCampaignRepository.saveAndFlush(emailCampaign);
|
this.emailCampaignRepository.saveAndFlush(emailCampaign);
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(emailCampaign.getId()).build();
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.withEntityId(emailCampaign.getId()) //
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -208,11 +158,7 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai
|
|||||||
if (!changes.isEmpty()) {
|
if (!changes.isEmpty()) {
|
||||||
this.emailCampaignRepository.saveAndFlush(emailCampaign);
|
this.emailCampaignRepository.saveAndFlush(emailCampaign);
|
||||||
}
|
}
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(resourceId).with(changes).build();
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.withEntityId(resourceId) //
|
|
||||||
.with(changes) //
|
|
||||||
.build();
|
|
||||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||||
final Throwable throwable = dve.getMostSpecificCause();
|
final Throwable throwable = dve.getMostSpecificCause();
|
||||||
handleDataIntegrityIssues(command, throwable, dve);
|
handleDataIntegrityIssues(command, throwable, dve);
|
||||||
@@ -319,56 +265,6 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai
|
|||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.UPDATE_EMAIL_OUTBOUND_WITH_CAMPAIGN_MESSAGE)
|
|
||||||
public void storeTemplateMessageIntoEmailOutBoundTable() throws JobExecutionException {
|
|
||||||
final Collection<EmailCampaignData> emailCampaignDataCollection = this.emailCampaignReadPlatformService
|
|
||||||
.retrieveAllScheduleActiveCampaign();
|
|
||||||
if (emailCampaignDataCollection != null) {
|
|
||||||
for (EmailCampaignData emailCampaignData : emailCampaignDataCollection) {
|
|
||||||
LocalDateTime tenantDateNow = tenantDateTime();
|
|
||||||
LocalDateTime nextTriggerDate = emailCampaignData.getNextTriggerDate().toLocalDateTime();
|
|
||||||
|
|
||||||
LOG.info("tenant time {} trigger time {}", tenantDateNow, nextTriggerDate);
|
|
||||||
if (nextTriggerDate.isBefore(tenantDateNow)) {
|
|
||||||
insertDirectCampaignIntoEmailOutboundTable(emailCampaignData.getParamValue(), emailCampaignData.getEmailSubject(),
|
|
||||||
emailCampaignData.getMessage(), emailCampaignData.getCampaignName(), emailCampaignData.getId());
|
|
||||||
this.updateTriggerDates(emailCampaignData.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTriggerDates(Long campaignId) {
|
|
||||||
final EmailCampaign emailCampaign = this.emailCampaignRepository.findById(campaignId)
|
|
||||||
.orElseThrow(() -> new EmailCampaignNotFound(campaignId));
|
|
||||||
LocalDateTime nextTriggerDate = emailCampaign.getNextTriggerDate();
|
|
||||||
emailCampaign.setLastTriggerDate(nextTriggerDate);
|
|
||||||
// calculate new trigger date and insert into next trigger date
|
|
||||||
|
|
||||||
/**
|
|
||||||
* next run time has to be in the future if not calculate a new future date
|
|
||||||
*/
|
|
||||||
LocalDateTime newTriggerDateWithTime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(),
|
|
||||||
emailCampaign.getNextTriggerDate(), nextTriggerDate);
|
|
||||||
if (newTriggerDateWithTime.isBefore(DateUtils.getLocalDateTimeOfTenant())) { // means
|
|
||||||
// next
|
|
||||||
// run
|
|
||||||
// time is
|
|
||||||
// in the
|
|
||||||
// past
|
|
||||||
// calculate
|
|
||||||
// a new
|
|
||||||
// future
|
|
||||||
// date
|
|
||||||
newTriggerDateWithTime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), emailCampaign.getNextTriggerDate(),
|
|
||||||
DateUtils.getLocalDateTimeOfTenant());
|
|
||||||
}
|
|
||||||
|
|
||||||
emailCampaign.setNextTriggerDate(newTriggerDateWithTime);
|
|
||||||
this.emailCampaignRepository.saveAndFlush(emailCampaign);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult activateEmailCampaign(Long campaignId, JsonCommand command) {
|
public CommandProcessingResult activateEmailCampaign(Long campaignId, JsonCommand command) {
|
||||||
@@ -456,7 +352,8 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "unused", "rawtypes" })
|
@SuppressWarnings({ "unused", "rawtypes" })
|
||||||
private List<HashMap<String, Object>> getRunReportByServiceImpl(final String reportName, final Map<String, String> queryParams)
|
@Override
|
||||||
|
public List<HashMap<String, Object>> getRunReportByServiceImpl(final String reportName, final Map<String, String> queryParams)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
final String reportType = "report";
|
final String reportType = "report";
|
||||||
|
|
||||||
@@ -585,248 +482,4 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai
|
|||||||
}
|
}
|
||||||
return today;
|
return today;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.EXECUTE_EMAIL)
|
|
||||||
public void sendEmailMessage() throws JobExecutionException {
|
|
||||||
if (IPv4Helper.applicationIsNotRunningOnLocalMachine()) { // remove when
|
|
||||||
// testing
|
|
||||||
// locally
|
|
||||||
final List<EmailMessage> emailMessages = this.emailMessageRepository
|
|
||||||
.findByStatusType(EmailMessageStatusType.PENDING.getValue()); // retrieve
|
|
||||||
// all
|
|
||||||
// pending
|
|
||||||
// message
|
|
||||||
|
|
||||||
for (final EmailMessage emailMessage : emailMessages) {
|
|
||||||
|
|
||||||
if (isValidEmail(emailMessage.getEmailAddress())) {
|
|
||||||
|
|
||||||
final EmailCampaign emailCampaign = this.emailCampaignRepository.findById(emailMessage.getEmailCampaign().getId())
|
|
||||||
.orElse(null); //
|
|
||||||
|
|
||||||
ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat = null;
|
|
||||||
if (emailCampaign.getEmailAttachmentFileFormat() != null) {
|
|
||||||
emailAttachmentFileFormat = ScheduledEmailAttachmentFileFormat
|
|
||||||
.instance(emailCampaign.getEmailAttachmentFileFormat());
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<File> attachmentList = new ArrayList<>();
|
|
||||||
|
|
||||||
final StringBuilder errorLog = new StringBuilder();
|
|
||||||
|
|
||||||
// check if email attachment format exist
|
|
||||||
if (emailAttachmentFileFormat != null && Arrays.asList(ScheduledEmailAttachmentFileFormat.validValues())
|
|
||||||
.contains(emailAttachmentFileFormat.getId())) {
|
|
||||||
|
|
||||||
final Report stretchyReport = emailCampaign.getStretchyReport();
|
|
||||||
|
|
||||||
final String reportName = (stretchyReport != null) ? stretchyReport.getReportName() : null;
|
|
||||||
|
|
||||||
final HashMap<String, String> reportStretchyParams = this
|
|
||||||
.validateStretchyReportParamMap(emailCampaign.getStretchyReportParamMap());
|
|
||||||
|
|
||||||
// there is a probability that a client has one or more
|
|
||||||
// loans or savings therefore we need to send two or
|
|
||||||
// more attachments
|
|
||||||
if (reportStretchyParams.containsKey("selectLoan") || reportStretchyParams.containsKey("loanId")) {
|
|
||||||
// get all ids of the client loans
|
|
||||||
if (emailMessage.getClient() != null) {
|
|
||||||
|
|
||||||
final List<Loan> loans = this.loanRepository.findLoanByClientId(emailMessage.getClient().getId());
|
|
||||||
|
|
||||||
HashMap<String, String> reportParams = this
|
|
||||||
.replaceStretchyParamsWithActualClientParams(reportStretchyParams, emailMessage.getClient());
|
|
||||||
|
|
||||||
for (final Loan loan : loans) {
|
|
||||||
if (loan.isOpen()) { // only send attachment
|
|
||||||
// for active loan
|
|
||||||
|
|
||||||
if (reportStretchyParams.containsKey("selectLoan")) {
|
|
||||||
|
|
||||||
reportParams.put("SelectLoan", loan.getId().toString());
|
|
||||||
|
|
||||||
} else if (reportStretchyParams.containsKey("loanId")) {
|
|
||||||
|
|
||||||
reportParams.put("loanId", loan.getId().toString());
|
|
||||||
}
|
|
||||||
File file = this.generateAttachments(emailCampaign, emailAttachmentFileFormat, reportParams,
|
|
||||||
reportName, errorLog);
|
|
||||||
|
|
||||||
if (file != null) {
|
|
||||||
attachmentList.add(file);
|
|
||||||
} else {
|
|
||||||
errorLog.append(reportParams.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} else if (reportStretchyParams.containsKey("savingId")) {
|
|
||||||
if (emailMessage.getClient() != null) {
|
|
||||||
|
|
||||||
final List<SavingsAccount> savingsAccounts = this.savingsAccountRepository
|
|
||||||
.findSavingAccountByClientId(emailMessage.getClient().getId());
|
|
||||||
|
|
||||||
HashMap<String, String> reportParams = this
|
|
||||||
.replaceStretchyParamsWithActualClientParams(reportStretchyParams, emailMessage.getClient());
|
|
||||||
|
|
||||||
for (final SavingsAccount savingsAccount : savingsAccounts) {
|
|
||||||
|
|
||||||
if (savingsAccount.isActive()) {
|
|
||||||
|
|
||||||
reportParams.put("savingId", savingsAccount.getId().toString());
|
|
||||||
|
|
||||||
File file = this.generateAttachments(emailCampaign, emailAttachmentFileFormat, reportParams,
|
|
||||||
reportName, errorLog);
|
|
||||||
|
|
||||||
if (file != null) {
|
|
||||||
attachmentList.add(file);
|
|
||||||
} else {
|
|
||||||
errorLog.append(reportParams.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (emailMessage.getClient() != null) {
|
|
||||||
|
|
||||||
HashMap<String, String> reportParams = this
|
|
||||||
.replaceStretchyParamsWithActualClientParams(reportStretchyParams, emailMessage.getClient());
|
|
||||||
|
|
||||||
File file = this.generateAttachments(emailCampaign, emailAttachmentFileFormat, reportParams, reportName,
|
|
||||||
errorLog);
|
|
||||||
|
|
||||||
if (file != null) {
|
|
||||||
attachmentList.add(file);
|
|
||||||
} else {
|
|
||||||
errorLog.append(reportParams.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
final EmailMessageWithAttachmentData emailMessageWithAttachmentData = EmailMessageWithAttachmentData.createNew(
|
|
||||||
emailMessage.getEmailAddress(), emailMessage.getMessage(), emailMessage.getEmailSubject(), attachmentList);
|
|
||||||
try {
|
|
||||||
|
|
||||||
this.emailMessageJobEmailService.sendEmailWithAttachment(emailMessageWithAttachmentData);
|
|
||||||
|
|
||||||
emailMessage.setStatusType(EmailMessageStatusType.SENT.getValue());
|
|
||||||
|
|
||||||
this.emailMessageRepository.save(emailMessage);
|
|
||||||
} catch (Exception e) {
|
|
||||||
emailMessage.updateErrorMessage(e.getMessage());
|
|
||||||
emailMessage.setStatusType(EmailMessageStatusType.FAILED.getValue());
|
|
||||||
this.emailMessageRepository.save(emailMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This generates the the report and converts it to a file by passing the parameters below
|
|
||||||
*
|
|
||||||
* @param emailCampaign
|
|
||||||
* @param emailAttachmentFileFormat
|
|
||||||
* @param reportParams
|
|
||||||
* @param reportName
|
|
||||||
* @param errorLog
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private File generateAttachments(final EmailCampaign emailCampaign, final ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat,
|
|
||||||
final Map<String, String> reportParams, final String reportName, final StringBuilder errorLog) {
|
|
||||||
if (reportName == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final ByteArrayOutputStream byteArrayOutputStream = this.readReportingService.generatePentahoReportAsOutputStream(reportName,
|
|
||||||
emailAttachmentFileFormat.getValue(), reportParams, null, emailCampaign.getApprovedBy(), errorLog);
|
|
||||||
|
|
||||||
final String fileLocation = FileSystemContentRepository.FINERACT_BASE_DIR + File.separator + "";
|
|
||||||
final String fileNameWithoutExtension = fileLocation + File.separator + reportName;
|
|
||||||
|
|
||||||
// check if file directory exists, if not create directory
|
|
||||||
if (!new File(fileLocation).isDirectory()) {
|
|
||||||
new File(fileLocation).mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byteArrayOutputStream.size() == 0) {
|
|
||||||
errorLog.append("Pentaho report processing failed, empty output stream created");
|
|
||||||
} else if (errorLog.length() == 0 && (byteArrayOutputStream.size() > 0)) {
|
|
||||||
final String fileName = fileNameWithoutExtension + "." + emailAttachmentFileFormat.getValue();
|
|
||||||
|
|
||||||
final File file = new File(fileName);
|
|
||||||
final FileOutputStream outputStream = new FileOutputStream(file);
|
|
||||||
byteArrayOutputStream.writeTo(outputStream);
|
|
||||||
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException | PlatformDataIntegrityException e) {
|
|
||||||
errorLog.append("The ReportMailingJobWritePlatformServiceImpl.executeReportMailingJobs threw an IOException " + "exception: "
|
|
||||||
+ e.getMessage() + " ---------- ");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This matches the the actual values to the key in the report stretchy parameters map
|
|
||||||
*
|
|
||||||
* @param stretchyParams
|
|
||||||
* @param client
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private HashMap<String, String> replaceStretchyParamsWithActualClientParams(final HashMap<String, String> stretchyParams,
|
|
||||||
final Client client) {
|
|
||||||
|
|
||||||
HashMap<String, String> actualParams = new HashMap<>();
|
|
||||||
|
|
||||||
for (Map.Entry<String, String> entry : stretchyParams.entrySet()) {
|
|
||||||
if (entry.getKey().equals("selectOffice")) {
|
|
||||||
// most at times the reports are run by picking the office of
|
|
||||||
// the staff Id
|
|
||||||
if (client.getStaff() != null) {
|
|
||||||
actualParams.put(entry.getKey(), client.getStaff().officeId().toString());
|
|
||||||
} else {
|
|
||||||
actualParams.put(entry.getKey(), client.getOffice().getId().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (entry.getKey().equals("selectClient")) {
|
|
||||||
|
|
||||||
actualParams.put(entry.getKey(), client.getId().toString());
|
|
||||||
|
|
||||||
} else if (entry.getKey().equals("selectLoanofficer")) {
|
|
||||||
|
|
||||||
actualParams.put(entry.getKey(), client.getStaff().getId().toString());
|
|
||||||
|
|
||||||
} else if (entry.getKey().equals("environementUrl")) {
|
|
||||||
|
|
||||||
actualParams.put(entry.getKey(), entry.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return actualParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HashMap<String, String> validateStretchyReportParamMap(final String stretchyParams) {
|
|
||||||
|
|
||||||
HashMap<String, String> stretchyReportParamHashMap = new HashMap<>();
|
|
||||||
|
|
||||||
if (!StringUtils.isEmpty(stretchyParams)) {
|
|
||||||
try {
|
|
||||||
stretchyReportParamHashMap = new ObjectMapper().readValue(stretchyParams, new TypeReference<HashMap<String, String>>() {});
|
|
||||||
}
|
|
||||||
|
|
||||||
catch (Exception e) {
|
|
||||||
stretchyReportParamHashMap = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stretchyReportParamHashMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -18,13 +18,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.campaigns.email.service;
|
package org.apache.fineract.infrastructure.campaigns.email.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.data.PreviewCampaignMessage;
|
import org.apache.fineract.infrastructure.campaigns.email.data.PreviewCampaignMessage;
|
||||||
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaign;
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaign;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonQuery;
|
import org.apache.fineract.infrastructure.core.api.JsonQuery;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
|
||||||
public interface EmailCampaignWritePlatformService {
|
public interface EmailCampaignWritePlatformService {
|
||||||
@@ -41,12 +43,11 @@ public interface EmailCampaignWritePlatformService {
|
|||||||
|
|
||||||
CommandProcessingResult reactivateEmailCampaign(Long campaignId, JsonCommand command);
|
CommandProcessingResult reactivateEmailCampaign(Long campaignId, JsonCommand command);
|
||||||
|
|
||||||
void storeTemplateMessageIntoEmailOutBoundTable() throws JobExecutionException;
|
@SuppressWarnings({ "unused", "rawtypes" })
|
||||||
|
List<HashMap<String, Object>> getRunReportByServiceImpl(String reportName, Map<String, String> queryParams) throws IOException;
|
||||||
|
|
||||||
PreviewCampaignMessage previewMessage(JsonQuery query);
|
PreviewCampaignMessage previewMessage(JsonQuery query);
|
||||||
|
|
||||||
void sendEmailMessage() throws JobExecutionException;
|
|
||||||
|
|
||||||
void insertDirectCampaignIntoEmailOutboundTable(Loan loan, EmailCampaign emailCampaign, HashMap<String, String> campaignParams);
|
void insertDirectCampaignIntoEmailOutboundTable(Loan loan, EmailCampaign emailCampaign, HashMap<String, String> campaignParams);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
|
||||||
|
public final class TenantDateTimeUtil {
|
||||||
|
|
||||||
|
private TenantDateTimeUtil() {}
|
||||||
|
|
||||||
|
public static LocalDateTime tenantDateTime() {
|
||||||
|
LocalDateTime today = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
|
final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
|
||||||
|
|
||||||
|
if (tenant != null) {
|
||||||
|
final ZoneId zone = ZoneId.of(tenant.getTimezoneId());
|
||||||
|
today = LocalDateTime.now(zone);
|
||||||
|
}
|
||||||
|
return today;
|
||||||
|
}
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.executeemail;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignRepository;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageRepository;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.service.EmailMessageJobEmailService;
|
||||||
|
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.validation.ReportMailingJobValidator;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ExecuteEmailConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private EmailMessageRepository emailMessageRepository;
|
||||||
|
@Autowired
|
||||||
|
private EmailCampaignRepository emailCampaignRepository;
|
||||||
|
@Autowired
|
||||||
|
private LoanRepository loanRepository;
|
||||||
|
@Autowired
|
||||||
|
private SavingsAccountRepository savingsAccountRepository;
|
||||||
|
@Autowired
|
||||||
|
private EmailMessageJobEmailService emailMessageJobEmailService;
|
||||||
|
@Autowired
|
||||||
|
private ReadReportingService readReportingService;
|
||||||
|
@Autowired
|
||||||
|
private ReportMailingJobValidator reportMailingJobValidator;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step executeEmailStep() {
|
||||||
|
return steps.get(JobName.EXECUTE_EMAIL.name()).tasklet(executeEmailTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job executeEmailJob() {
|
||||||
|
return jobs.get(JobName.EXECUTE_EMAIL.name()).start(executeEmailStep()).incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ExecuteEmailTasklet executeEmailTasklet() {
|
||||||
|
return new ExecuteEmailTasklet(emailMessageRepository, emailCampaignRepository, loanRepository, savingsAccountRepository,
|
||||||
|
emailMessageJobEmailService, readReportingService, reportMailingJobValidator);
|
||||||
|
}
|
||||||
|
}
|
||||||
+229
@@ -0,0 +1,229 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.executeemail;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.mail.internet.AddressException;
|
||||||
|
import javax.mail.internet.InternetAddress;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.data.EmailMessageWithAttachmentData;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaign;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignRepository;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessage;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageRepository;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageStatusType;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.ScheduledEmailAttachmentFileFormat;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.service.EmailMessageJobEmailService;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||||
|
import org.apache.fineract.infrastructure.dataqueries.domain.Report;
|
||||||
|
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||||
|
import org.apache.fineract.infrastructure.documentmanagement.contentrepository.FileSystemContentRepository;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.helper.IPv4Helper;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.validation.ReportMailingJobValidator;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ExecuteEmailTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final EmailMessageRepository emailMessageRepository;
|
||||||
|
private final EmailCampaignRepository emailCampaignRepository;
|
||||||
|
private final LoanRepository loanRepository;
|
||||||
|
private final SavingsAccountRepository savingsAccountRepository;
|
||||||
|
private final EmailMessageJobEmailService emailMessageJobEmailService;
|
||||||
|
private final ReadReportingService readReportingService;
|
||||||
|
private final ReportMailingJobValidator reportMailingJobValidator;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
if (IPv4Helper.applicationIsNotRunningOnLocalMachine()) {
|
||||||
|
final List<EmailMessage> emailMessages = emailMessageRepository.findByStatusType(EmailMessageStatusType.PENDING.getValue());
|
||||||
|
for (final EmailMessage emailMessage : emailMessages) {
|
||||||
|
if (isValidEmail(emailMessage.getEmailAddress())) {
|
||||||
|
final EmailCampaign emailCampaign = emailCampaignRepository.findById(emailMessage.getEmailCampaign().getId())
|
||||||
|
.orElse(null); //
|
||||||
|
ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat = null;
|
||||||
|
if (emailCampaign.getEmailAttachmentFileFormat() != null) {
|
||||||
|
emailAttachmentFileFormat = ScheduledEmailAttachmentFileFormat
|
||||||
|
.instance(emailCampaign.getEmailAttachmentFileFormat());
|
||||||
|
}
|
||||||
|
final List<File> attachmentList = new ArrayList<>();
|
||||||
|
final StringBuilder errorLog = new StringBuilder();
|
||||||
|
if (emailAttachmentFileFormat != null && Arrays.asList(ScheduledEmailAttachmentFileFormat.validValues())
|
||||||
|
.contains(emailAttachmentFileFormat.getId())) {
|
||||||
|
final Report stretchyReport = emailCampaign.getStretchyReport();
|
||||||
|
final String reportName = (stretchyReport != null) ? stretchyReport.getReportName() : null;
|
||||||
|
final HashMap<String, String> reportStretchyParams = reportMailingJobValidator
|
||||||
|
.validateStretchyReportParamMap(emailCampaign.getStretchyReportParamMap());
|
||||||
|
if (reportStretchyParams.containsKey("selectLoan") || reportStretchyParams.containsKey("loanId")) {
|
||||||
|
if (emailMessage.getClient() != null) {
|
||||||
|
final List<Loan> loans = loanRepository.findLoanByClientId(emailMessage.getClient().getId());
|
||||||
|
HashMap<String, String> reportParams = replaceStretchyParamsWithActualClientParams(reportStretchyParams,
|
||||||
|
emailMessage.getClient());
|
||||||
|
for (final Loan loan : loans) {
|
||||||
|
if (loan.isOpen()) {
|
||||||
|
if (reportStretchyParams.containsKey("selectLoan")) {
|
||||||
|
reportParams.put("SelectLoan", loan.getId().toString());
|
||||||
|
} else if (reportStretchyParams.containsKey("loanId")) {
|
||||||
|
reportParams.put("loanId", loan.getId().toString());
|
||||||
|
}
|
||||||
|
File file = generateAttachments(emailCampaign, emailAttachmentFileFormat, reportParams, reportName,
|
||||||
|
errorLog);
|
||||||
|
if (file != null) {
|
||||||
|
attachmentList.add(file);
|
||||||
|
} else {
|
||||||
|
errorLog.append(reportParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (reportStretchyParams.containsKey("savingId")) {
|
||||||
|
if (emailMessage.getClient() != null) {
|
||||||
|
final List<SavingsAccount> savingsAccounts = savingsAccountRepository
|
||||||
|
.findSavingAccountByClientId(emailMessage.getClient().getId());
|
||||||
|
HashMap<String, String> reportParams = replaceStretchyParamsWithActualClientParams(reportStretchyParams,
|
||||||
|
emailMessage.getClient());
|
||||||
|
for (final SavingsAccount savingsAccount : savingsAccounts) {
|
||||||
|
if (savingsAccount.isActive()) {
|
||||||
|
reportParams.put("savingId", savingsAccount.getId().toString());
|
||||||
|
File file = generateAttachments(emailCampaign, emailAttachmentFileFormat, reportParams, reportName,
|
||||||
|
errorLog);
|
||||||
|
if (file != null) {
|
||||||
|
attachmentList.add(file);
|
||||||
|
} else {
|
||||||
|
errorLog.append(reportParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (emailMessage.getClient() != null) {
|
||||||
|
HashMap<String, String> reportParams = replaceStretchyParamsWithActualClientParams(reportStretchyParams,
|
||||||
|
emailMessage.getClient());
|
||||||
|
File file = generateAttachments(emailCampaign, emailAttachmentFileFormat, reportParams, reportName,
|
||||||
|
errorLog);
|
||||||
|
if (file != null) {
|
||||||
|
attachmentList.add(file);
|
||||||
|
} else {
|
||||||
|
errorLog.append(reportParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final EmailMessageWithAttachmentData emailMessageWithAttachmentData = EmailMessageWithAttachmentData.createNew(
|
||||||
|
emailMessage.getEmailAddress(), emailMessage.getMessage(), emailMessage.getEmailSubject(), attachmentList);
|
||||||
|
try {
|
||||||
|
emailMessageJobEmailService.sendEmailWithAttachment(emailMessageWithAttachmentData);
|
||||||
|
emailMessage.setStatusType(EmailMessageStatusType.SENT.getValue());
|
||||||
|
emailMessageRepository.save(emailMessage);
|
||||||
|
} catch (Exception e) {
|
||||||
|
emailMessage.updateErrorMessage(e.getMessage());
|
||||||
|
emailMessage.setStatusType(EmailMessageStatusType.FAILED.getValue());
|
||||||
|
emailMessageRepository.save(emailMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidEmail(String email) {
|
||||||
|
boolean isValid = true;
|
||||||
|
try {
|
||||||
|
InternetAddress emailO = new InternetAddress(email);
|
||||||
|
emailO.validate();
|
||||||
|
} catch (AddressException ex) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap<String, String> replaceStretchyParamsWithActualClientParams(final HashMap<String, String> stretchyParams,
|
||||||
|
final Client client) {
|
||||||
|
HashMap<String, String> actualParams = new HashMap<>();
|
||||||
|
for (Map.Entry<String, String> entry : stretchyParams.entrySet()) {
|
||||||
|
switch (entry.getKey()) {
|
||||||
|
case "selectOffice":
|
||||||
|
if (client.getStaff() != null) {
|
||||||
|
actualParams.put(entry.getKey(), client.getStaff().officeId().toString());
|
||||||
|
} else {
|
||||||
|
actualParams.put(entry.getKey(), client.getOffice().getId().toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "selectClient":
|
||||||
|
actualParams.put(entry.getKey(), client.getId().toString());
|
||||||
|
break;
|
||||||
|
case "selectLoanofficer":
|
||||||
|
actualParams.put(entry.getKey(), client.getStaff().getId().toString());
|
||||||
|
break;
|
||||||
|
case "environementUrl":
|
||||||
|
actualParams.put(entry.getKey(), entry.getKey());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actualParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File generateAttachments(final EmailCampaign emailCampaign, final ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat,
|
||||||
|
final Map<String, String> reportParams, final String reportName, final StringBuilder errorLog) {
|
||||||
|
if (reportName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final ByteArrayOutputStream byteArrayOutputStream = readReportingService.generatePentahoReportAsOutputStream(reportName,
|
||||||
|
emailAttachmentFileFormat.getValue(), reportParams, null, emailCampaign.getApprovedBy(), errorLog);
|
||||||
|
final String fileLocation = FileSystemContentRepository.FINERACT_BASE_DIR + File.separator + "";
|
||||||
|
final String fileNameWithoutExtension = fileLocation + File.separator + reportName;
|
||||||
|
if (!new File(fileLocation).isDirectory()) {
|
||||||
|
new File(fileLocation).mkdirs();
|
||||||
|
}
|
||||||
|
if (byteArrayOutputStream.size() == 0) {
|
||||||
|
errorLog.append("Pentaho report processing failed, empty output stream created");
|
||||||
|
} else if (errorLog.length() == 0 && (byteArrayOutputStream.size() > 0)) {
|
||||||
|
final String fileName = fileNameWithoutExtension + "." + emailAttachmentFileFormat.getValue();
|
||||||
|
|
||||||
|
final File file = new File(fileName);
|
||||||
|
final FileOutputStream outputStream = new FileOutputStream(file);
|
||||||
|
byteArrayOutputStream.writeTo(outputStream);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
} catch (IOException | PlatformDataIntegrityException e) {
|
||||||
|
errorLog.append("The ReportMailingJobWritePlatformServiceImpl.executeReportMailingJobs threw an IOException " + "exception: ")
|
||||||
|
.append(e.getMessage()).append(" ---------- ");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.executereportmailingjobs;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.infrastructure.report.provider.ReportingProcessServiceProvider;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRepository;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRunHistoryRepository;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.service.ReportMailingJobEmailService;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.validation.ReportMailingJobValidator;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ExecuteReportMailingJobsConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private ReportMailingJobRepository reportMailingJobRepository;
|
||||||
|
@Autowired
|
||||||
|
private ReportMailingJobValidator reportMailingJobValidator;
|
||||||
|
@Autowired
|
||||||
|
private ReadReportingService readReportingService;
|
||||||
|
@Autowired
|
||||||
|
private ReportingProcessServiceProvider reportingProcessServiceProvider;
|
||||||
|
@Autowired
|
||||||
|
private ReportMailingJobEmailService reportMailingJobEmailService;
|
||||||
|
@Autowired
|
||||||
|
private ReportMailingJobRunHistoryRepository reportMailingJobRunHistoryRepository;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step executeReportMailingJobsStep() {
|
||||||
|
return steps.get(JobName.EXECUTE_REPORT_MAILING_JOBS.name()).tasklet(executeReportMailingJobsTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job executeReportMailingJobsJob() {
|
||||||
|
return jobs.get(JobName.EXECUTE_REPORT_MAILING_JOBS.name()).start(executeReportMailingJobsStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ExecuteReportMailingJobsTasklet executeReportMailingJobsTasklet() {
|
||||||
|
return new ExecuteReportMailingJobsTasklet(reportMailingJobRepository, reportMailingJobValidator, readReportingService,
|
||||||
|
reportingProcessServiceProvider, reportMailingJobEmailService, reportMailingJobRunHistoryRepository);
|
||||||
|
}
|
||||||
|
}
|
||||||
+245
@@ -0,0 +1,245 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.executereportmailingjobs;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.dataqueries.domain.Report;
|
||||||
|
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||||
|
import org.apache.fineract.infrastructure.documentmanagement.contentrepository.FileSystemContentRepository;
|
||||||
|
import org.apache.fineract.infrastructure.report.provider.ReportingProcessServiceProvider;
|
||||||
|
import org.apache.fineract.infrastructure.report.service.ReportingProcessService;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailAttachmentFileFormat;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailData;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobPreviousRunStatus;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobStretchyReportParamDateOption;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJob;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRepository;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRunHistory;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRunHistoryRepository;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.service.ReportMailingJobEmailService;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.util.ReportMailingJobDateUtil;
|
||||||
|
import org.apache.fineract.infrastructure.reportmailingjob.validation.ReportMailingJobValidator;
|
||||||
|
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||||
|
import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ExecuteReportMailingJobsTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final ReportMailingJobRepository reportMailingJobRepository;
|
||||||
|
private final ReportMailingJobValidator reportMailingJobValidator;
|
||||||
|
private final ReadReportingService readReportingService;
|
||||||
|
private final ReportingProcessServiceProvider reportingProcessServiceProvider;
|
||||||
|
private final ReportMailingJobEmailService reportMailingJobEmailService;
|
||||||
|
private final ReportMailingJobRunHistoryRepository reportMailingJobRunHistoryRepository;
|
||||||
|
|
||||||
|
private static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
final Collection<ReportMailingJob> reportMailingJobCollection = reportMailingJobRepository.findByIsActiveTrueAndIsDeletedFalse();
|
||||||
|
|
||||||
|
for (ReportMailingJob reportMailingJob : reportMailingJobCollection) {
|
||||||
|
final LocalDateTime localDateTimeOftenant = DateUtils.getLocalDateTimeOfTenant();
|
||||||
|
final LocalDateTime nextRunDateTime = reportMailingJob.getNextRunDateTime();
|
||||||
|
|
||||||
|
if (nextRunDateTime != null && nextRunDateTime.isBefore(localDateTimeOftenant)) {
|
||||||
|
final ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat = ReportMailingJobEmailAttachmentFileFormat
|
||||||
|
.newInstance(reportMailingJob.getEmailAttachmentFileFormat());
|
||||||
|
|
||||||
|
if (emailAttachmentFileFormat != null && emailAttachmentFileFormat.isValid()) {
|
||||||
|
final Report stretchyReport = reportMailingJob.getStretchyReport();
|
||||||
|
final String reportName = (stretchyReport != null) ? stretchyReport.getReportName() : null;
|
||||||
|
final StringBuilder errorLog = new StringBuilder();
|
||||||
|
final Map<String, String> validateStretchyReportParamMap = reportMailingJobValidator
|
||||||
|
.validateStretchyReportParamMap(reportMailingJob.getStretchyReportParamMap());
|
||||||
|
MultivaluedMap<String, String> reportParams = new MultivaluedStringMap();
|
||||||
|
|
||||||
|
if (validateStretchyReportParamMap != null) {
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> validateStretchyReportParamMapEntry : validateStretchyReportParamMap.entrySet()) {
|
||||||
|
String key = validateStretchyReportParamMapEntry.getKey();
|
||||||
|
String value = validateStretchyReportParamMapEntry.getValue();
|
||||||
|
|
||||||
|
if (StringUtils.containsIgnoreCase(key, "date")) {
|
||||||
|
ReportMailingJobStretchyReportParamDateOption reportMailingJobStretchyReportParamDateOption = ReportMailingJobStretchyReportParamDateOption
|
||||||
|
.newInstance(value);
|
||||||
|
|
||||||
|
if (reportMailingJobStretchyReportParamDateOption.isValid()) {
|
||||||
|
value = ReportMailingJobDateUtil.getDateAsString(reportMailingJobStretchyReportParamDateOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reportParams.add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateReportOutputStream(reportMailingJob, emailAttachmentFileFormat, reportParams, reportName, errorLog);
|
||||||
|
|
||||||
|
updateReportMailingJobAfterJobExecution(reportMailingJob, errorLog, localDateTimeOftenant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateReportOutputStream(final ReportMailingJob reportMailingJob,
|
||||||
|
final ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat, final MultivaluedMap<String, String> reportParams,
|
||||||
|
final String reportName, final StringBuilder errorLog) {
|
||||||
|
try {
|
||||||
|
final boolean isSelfServiceUserReport = false;
|
||||||
|
final String reportType = readReportingService.getReportType(reportName, isSelfServiceUserReport, false);
|
||||||
|
final ReportingProcessService reportingProcessService = reportingProcessServiceProvider.findReportingProcessService(reportType);
|
||||||
|
|
||||||
|
if (reportingProcessService != null) {
|
||||||
|
final Response processReport = reportingProcessService.processRequest(reportName, reportParams);
|
||||||
|
final Object responseObject = (processReport != null) ? processReport.getEntity() : null;
|
||||||
|
|
||||||
|
if (responseObject != null && responseObject.getClass().equals(ByteArrayOutputStream.class)) {
|
||||||
|
final ByteArrayOutputStream byteArrayOutputStream = (ByteArrayOutputStream) responseObject;
|
||||||
|
final String fileLocation = FileSystemContentRepository.FINERACT_BASE_DIR + File.separator + "";
|
||||||
|
final String fileNameWithoutExtension = fileLocation + File.separator + reportName;
|
||||||
|
|
||||||
|
if (!new File(fileLocation).isDirectory()) {
|
||||||
|
new File(fileLocation).mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byteArrayOutputStream.size() == 0) {
|
||||||
|
errorLog.append("Report processing failed, empty output stream created");
|
||||||
|
} else if ((errorLog != null && errorLog.length() == 0) && (byteArrayOutputStream.size() > 0)) {
|
||||||
|
final String fileName = fileNameWithoutExtension + "." + emailAttachmentFileFormat.getValue();
|
||||||
|
|
||||||
|
sendReportFileToEmailRecipients(reportMailingJob, fileName, byteArrayOutputStream, errorLog);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorLog.append("Response object entity is not equal to ByteArrayOutputStream ---------- ");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorLog.append(ReportingProcessServiceProvider.SERVICE_MISSING).append(reportType);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
errorLog.append("The ReportMailingJobWritePlatformServiceImpl.generateReportOutputStream method threw an Exception: ").append(e)
|
||||||
|
.append(" ---------- ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateReportMailingJobAfterJobExecution(final ReportMailingJob reportMailingJob, final StringBuilder errorLog,
|
||||||
|
final LocalDateTime jobStartDateTime) {
|
||||||
|
final String recurrence = reportMailingJob.getRecurrence();
|
||||||
|
final LocalDateTime nextRunDateTime = reportMailingJob.getNextRunDateTime();
|
||||||
|
ReportMailingJobPreviousRunStatus reportMailingJobPreviousRunStatus = ReportMailingJobPreviousRunStatus.SUCCESS;
|
||||||
|
|
||||||
|
reportMailingJob.updatePreviousRunErrorLog(null);
|
||||||
|
|
||||||
|
if (errorLog != null && errorLog.length() > 0) {
|
||||||
|
reportMailingJobPreviousRunStatus = ReportMailingJobPreviousRunStatus.ERROR;
|
||||||
|
reportMailingJob.updatePreviousRunErrorLog(errorLog.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
reportMailingJob.increaseNumberOfRunsByOne();
|
||||||
|
reportMailingJob.updatePreviousRunStatus(reportMailingJobPreviousRunStatus.getValue());
|
||||||
|
reportMailingJob.updatePreviousRunDateTime(reportMailingJob.getNextRunDateTime());
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(recurrence)) {
|
||||||
|
reportMailingJob.deactivate();
|
||||||
|
|
||||||
|
reportMailingJob.updateNextRunDateTime(null);
|
||||||
|
} else if (nextRunDateTime != null) {
|
||||||
|
final LocalDateTime nextRecurringDateTime = createNextRecurringDateTime(recurrence, nextRunDateTime);
|
||||||
|
|
||||||
|
reportMailingJob.updateNextRunDateTime(nextRecurringDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
reportMailingJobRepository.save(reportMailingJob);
|
||||||
|
|
||||||
|
createReportMailingJobRunHistroryAfterJobExecution(reportMailingJob, errorLog, jobStartDateTime,
|
||||||
|
reportMailingJobPreviousRunStatus.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendReportFileToEmailRecipients(final ReportMailingJob reportMailingJob, final String fileName,
|
||||||
|
final ByteArrayOutputStream byteArrayOutputStream, final StringBuilder errorLog) {
|
||||||
|
final Set<String> emailRecipients = this.reportMailingJobValidator.validateEmailRecipients(reportMailingJob.getEmailRecipients());
|
||||||
|
|
||||||
|
try {
|
||||||
|
final File file = new File(fileName);
|
||||||
|
final FileOutputStream outputStream = new FileOutputStream(file);
|
||||||
|
byteArrayOutputStream.writeTo(outputStream);
|
||||||
|
|
||||||
|
for (String emailRecipient : emailRecipients) {
|
||||||
|
final ReportMailingJobEmailData reportMailingJobEmailData = new ReportMailingJobEmailData(emailRecipient,
|
||||||
|
reportMailingJob.getEmailMessage(), reportMailingJob.getEmailSubject(), file);
|
||||||
|
|
||||||
|
reportMailingJobEmailService.sendEmailWithAttachment(reportMailingJobEmailData);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputStream.close();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
errorLog.append("The ReportMailingJobWritePlatformServiceImpl.sendReportFileToEmailRecipients method threw an IOException "
|
||||||
|
+ "exception: ").append(e).append(" ---------- ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDateTime createNextRecurringDateTime(final String recurrencePattern, final LocalDateTime startDateTime) {
|
||||||
|
LocalDateTime nextRecurringDateTime = null;
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(recurrencePattern) && startDateTime != null) {
|
||||||
|
final LocalDate nextDayLocalDate = startDateTime.plus(Duration.ofDays(1)).toLocalDate();
|
||||||
|
final LocalDate nextRecurringLocalDate = CalendarUtils.getNextRecurringDate(recurrencePattern, startDateTime.toLocalDate(),
|
||||||
|
nextDayLocalDate);
|
||||||
|
final String nextDateTimeString = nextRecurringLocalDate + " " + startDateTime.getHour() + ":" + startDateTime.getMinute() + ":"
|
||||||
|
+ startDateTime.getSecond();
|
||||||
|
final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_FORMAT);
|
||||||
|
|
||||||
|
nextRecurringDateTime = LocalDateTime.parse(nextDateTimeString, dateTimeFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextRecurringDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createReportMailingJobRunHistroryAfterJobExecution(final ReportMailingJob reportMailingJob, final StringBuilder errorLog,
|
||||||
|
final LocalDateTime jobStartDateTime, final String jobRunStatus) {
|
||||||
|
final LocalDateTime jobEndDateTime = DateUtils.getLocalDateTimeOfTenant();
|
||||||
|
final String errorLogToString = (errorLog != null) ? errorLog.toString() : null;
|
||||||
|
final ReportMailingJobRunHistory reportMailingJobRunHistory = ReportMailingJobRunHistory.newInstance(reportMailingJob,
|
||||||
|
jobStartDateTime, jobEndDateTime, jobRunStatus, null, errorLogToString);
|
||||||
|
|
||||||
|
reportMailingJobRunHistoryRepository.save(reportMailingJobRunHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.getdeliveryreportsfromsmsgateway;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.helper.SmsConfigUtils;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
||||||
|
import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class GetDeliveryReportsFromSmsGatewayConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private SmsMessageRepository smsMessageRepository;
|
||||||
|
@Autowired
|
||||||
|
private SmsReadPlatformService smsReadPlatformService;
|
||||||
|
@Autowired
|
||||||
|
private SmsConfigUtils smsConfigUtils;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step getDeliveryReportsFromSmsGatewayStep() {
|
||||||
|
return steps.get(JobName.GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY.name()).tasklet(getDeliveryReportsFromSmsGatewayTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job getDeliveryReportsFromSmsGatewayJob() {
|
||||||
|
return jobs.get(JobName.GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY.name()).start(getDeliveryReportsFromSmsGatewayStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GetDeliveryReportsFromSmsGatewayTasklet getDeliveryReportsFromSmsGatewayTasklet() {
|
||||||
|
return new GetDeliveryReportsFromSmsGatewayTasklet(smsReadPlatformService, smsConfigUtils, smsMessageRepository);
|
||||||
|
}
|
||||||
|
}
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.getdeliveryreportsfromsmsgateway;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.helper.SmsConfigUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.Page;
|
||||||
|
import org.apache.fineract.infrastructure.sms.data.SmsMessageDeliveryReportData;
|
||||||
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
|
||||||
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
||||||
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
|
||||||
|
import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GetDeliveryReportsFromSmsGatewayTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final SmsReadPlatformService smsReadPlatformService;
|
||||||
|
private final SmsConfigUtils smsConfigUtils;
|
||||||
|
private final SmsMessageRepository smsMessageRepository;
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
int page = 0;
|
||||||
|
int totalRecords;
|
||||||
|
Integer limit = 200;
|
||||||
|
do {
|
||||||
|
Page<Long> smsMessageInternalIds = smsReadPlatformService.retrieveAllWaitingForDeliveryReport(limit);
|
||||||
|
try {
|
||||||
|
if (!CollectionUtils.isEmpty(smsMessageInternalIds.getPageItems())) {
|
||||||
|
Map<String, Object> hostConfig = smsConfigUtils.getMessageGateWayRequestURI("sms/report",
|
||||||
|
new Gson().toJson(smsMessageInternalIds.getPageItems()));
|
||||||
|
URI uri = (URI) hostConfig.get("uri");
|
||||||
|
HttpEntity<?> entity = (HttpEntity<?>) hostConfig.get("entity");
|
||||||
|
ResponseEntity<Collection<SmsMessageDeliveryReportData>> responseOne = restTemplate.exchange(uri, HttpMethod.POST,
|
||||||
|
entity, new ParameterizedTypeReference<>() {});
|
||||||
|
|
||||||
|
Collection<SmsMessageDeliveryReportData> smsMessageDeliveryReportDataCollection = responseOne.getBody();
|
||||||
|
if (!CollectionUtils.isEmpty(smsMessageDeliveryReportDataCollection)) {
|
||||||
|
for (SmsMessageDeliveryReportData smsMessageDeliveryReportData : smsMessageDeliveryReportDataCollection) {
|
||||||
|
Integer deliveryStatus = smsMessageDeliveryReportData.getDeliveryStatus();
|
||||||
|
|
||||||
|
if (!smsMessageDeliveryReportData.getHasError() && deliveryStatus != 100) {
|
||||||
|
SmsMessage smsMessage = smsMessageRepository.findById(smsMessageDeliveryReportData.getId()).orElse(null);
|
||||||
|
Integer statusType = switch (deliveryStatus) {
|
||||||
|
case 0 -> SmsMessageStatusType.INVALID.getValue();
|
||||||
|
case 150 -> SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue();
|
||||||
|
case 200 -> SmsMessageStatusType.SENT.getValue();
|
||||||
|
case 300 -> SmsMessageStatusType.DELIVERED.getValue();
|
||||||
|
case 400 -> SmsMessageStatusType.FAILED.getValue();
|
||||||
|
default -> smsMessage.getStatusType();
|
||||||
|
};
|
||||||
|
|
||||||
|
boolean statusChanged = !statusType.equals(smsMessage.getStatusType());
|
||||||
|
smsMessage.setStatusType(statusType);
|
||||||
|
smsMessage.setExternalId(smsMessageDeliveryReportData.getExternalId());
|
||||||
|
smsMessageRepository.save(smsMessage);
|
||||||
|
|
||||||
|
if (statusChanged) {
|
||||||
|
log.info("Status of SMS message id: {} successfully changed to {}", smsMessage.getId(), statusType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!CollectionUtils.isEmpty(smsMessageDeliveryReportDataCollection)) {
|
||||||
|
log.info("{} delivery report(s) successfully received from the intermediate gateway - sms",
|
||||||
|
smsMessageDeliveryReportDataCollection.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("Error occurred.", e);
|
||||||
|
}
|
||||||
|
page++;
|
||||||
|
totalRecords = smsMessageInternalIds.getTotalFilteredRecords();
|
||||||
|
} while (page < totalRecords);
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.sendmessagetosmsgateway;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.helper.SmsConfigUtils;
|
||||||
|
import org.apache.fineract.infrastructure.gcm.service.NotificationSenderService;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class SendMessageToSmsGatewayConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private SmsMessageRepository smsMessageRepository;
|
||||||
|
@Autowired
|
||||||
|
private NotificationSenderService notificationSenderService;
|
||||||
|
@Autowired
|
||||||
|
private SmsConfigUtils smsConfigUtils;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step sendMessageToSmsGatewayStep() {
|
||||||
|
return steps.get(JobName.SEND_MESSAGES_TO_SMS_GATEWAY.name()).tasklet(sendMessageToSmsGatewayTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job sendMessageToSmsGatewayJob() {
|
||||||
|
return jobs.get(JobName.SEND_MESSAGES_TO_SMS_GATEWAY.name()).start(sendMessageToSmsGatewayStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SendMessageToSmsGatewayTasklet sendMessageToSmsGatewayTasklet() {
|
||||||
|
return new SendMessageToSmsGatewayTasklet(smsMessageRepository, notificationSenderService, smsConfigUtils);
|
||||||
|
}
|
||||||
|
}
|
||||||
+154
@@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.sendmessagetosmsgateway;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.helper.SmsConfigUtils;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignConstants;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.sms.exception.ConnectionFailureException;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.infrastructure.gcm.service.NotificationSenderService;
|
||||||
|
import org.apache.fineract.infrastructure.sms.data.SmsMessageApiQueueResourceData;
|
||||||
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
|
||||||
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
||||||
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.event.ContextClosedEvent;
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SendMessageToSmsGatewayTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final SmsMessageRepository smsMessageRepository;
|
||||||
|
private ExecutorService genericExecutorService;
|
||||||
|
private final NotificationSenderService notificationSenderService;
|
||||||
|
private final SmsConfigUtils smsConfigUtils;
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initializeExecutorService() {
|
||||||
|
genericExecutorService = Executors.newSingleThreadExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
int pageLimit = 200;
|
||||||
|
int page = 0;
|
||||||
|
int totalRecords;
|
||||||
|
do {
|
||||||
|
PageRequest pageRequest = PageRequest.of(0, pageLimit);
|
||||||
|
org.springframework.data.domain.Page<SmsMessage> pendingMessages = smsMessageRepository
|
||||||
|
.findByStatusType(SmsMessageStatusType.PENDING.getValue(), pageRequest);
|
||||||
|
List<SmsMessage> toSaveMessages = new ArrayList<>();
|
||||||
|
List<SmsMessage> toSendNotificationMessages = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
if (!CollectionUtils.isEmpty(pendingMessages.getContent())) {
|
||||||
|
final String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
|
||||||
|
Iterator<SmsMessage> pendingMessageIterator = pendingMessages.iterator();
|
||||||
|
Collection<SmsMessageApiQueueResourceData> apiQueueResourceDataCollection = new ArrayList<>();
|
||||||
|
while (pendingMessageIterator.hasNext()) {
|
||||||
|
SmsMessage smsData = pendingMessageIterator.next();
|
||||||
|
if (smsData.isNotification()) {
|
||||||
|
smsData.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
|
||||||
|
toSendNotificationMessages.add(smsData);
|
||||||
|
} else {
|
||||||
|
SmsMessageApiQueueResourceData apiQueueResourceData = SmsMessageApiQueueResourceData.instance(smsData.getId(),
|
||||||
|
tenantIdentifier, null, null, smsData.getMobileNo(), smsData.getMessage(),
|
||||||
|
smsData.getSmsCampaign().getProviderId());
|
||||||
|
apiQueueResourceDataCollection.add(apiQueueResourceData);
|
||||||
|
smsData.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
|
||||||
|
toSaveMessages.add(smsData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!toSaveMessages.isEmpty()) {
|
||||||
|
smsMessageRepository.saveAll(toSaveMessages);
|
||||||
|
smsMessageRepository.flush();
|
||||||
|
genericExecutorService.execute(new SmsTask(ThreadLocalContextUtil.getTenant(), apiQueueResourceDataCollection));
|
||||||
|
}
|
||||||
|
if (!toSendNotificationMessages.isEmpty()) {
|
||||||
|
notificationSenderService.sendNotification(toSendNotificationMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ConnectionFailureException(SmsCampaignConstants.SMS, e);
|
||||||
|
}
|
||||||
|
page++;
|
||||||
|
totalRecords = pendingMessages.getTotalPages();
|
||||||
|
} while (page < totalRecords);
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SmsTask implements Runnable, ApplicationListener<ContextClosedEvent> {
|
||||||
|
|
||||||
|
private final FineractPlatformTenant tenant;
|
||||||
|
private final Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas;
|
||||||
|
|
||||||
|
SmsTask(final FineractPlatformTenant tenant, final Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas) {
|
||||||
|
this.tenant = tenant;
|
||||||
|
this.apiQueueResourceDatas = apiQueueResourceDatas;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ThreadLocalContextUtil.setTenant(tenant);
|
||||||
|
connectAndSendToIntermediateServer(apiQueueResourceDatas);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ContextClosedEvent event) {
|
||||||
|
genericExecutorService.shutdown();
|
||||||
|
log.info("Shutting down the ExecutorService");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectAndSendToIntermediateServer(Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas) {
|
||||||
|
Map<String, Object> hostConfig = smsConfigUtils.getMessageGateWayRequestURI("sms",
|
||||||
|
SmsMessageApiQueueResourceData.toJsonString(apiQueueResourceDatas));
|
||||||
|
URI uri = (URI) hostConfig.get("uri");
|
||||||
|
HttpEntity<?> entity = (HttpEntity<?>) hostConfig.get("entity");
|
||||||
|
ResponseEntity<String> responseOne = restTemplate.exchange(uri, HttpMethod.POST, entity, new ParameterizedTypeReference<>() {});
|
||||||
|
if (!responseOne.getStatusCode().equals(HttpStatus.ACCEPTED)) {
|
||||||
|
log.debug("{}", responseOne.getStatusCode().name());
|
||||||
|
throw new ConnectionFailureException(SmsCampaignConstants.SMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-10
@@ -16,16 +16,11 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.infrastructure.campaigns.jobs.updateemailoutboundwithcampaignmessage;
|
||||||
|
|
||||||
import java.util.Map;
|
public class EmailParamMappingException extends RuntimeException {
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
|
||||||
|
|
||||||
public interface LoanSchedularService {
|
public EmailParamMappingException(Exception e) {
|
||||||
|
super(e);
|
||||||
void applyChargeForOverdueLoans() throws JobExecutionException;
|
}
|
||||||
|
|
||||||
void recalculateInterest() throws JobExecutionException;
|
|
||||||
|
|
||||||
void recalculateInterest(@SuppressWarnings("unused") Map<String, String> jobParameters);
|
|
||||||
}
|
}
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.updateemailoutboundwithcampaignmessage;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignRepository;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageRepository;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.service.EmailCampaignReadPlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.service.EmailCampaignWritePlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class UpdateEmailOutboundWithCampaignMessageConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private EmailCampaignReadPlatformService emailCampaignReadPlatformService;
|
||||||
|
@Autowired
|
||||||
|
private EmailCampaignRepository emailCampaignRepository;
|
||||||
|
@Autowired
|
||||||
|
private ClientRepositoryWrapper clientRepositoryWrapper;
|
||||||
|
@Autowired
|
||||||
|
private EmailMessageRepository emailMessageRepository;
|
||||||
|
@Autowired
|
||||||
|
private EmailCampaignWritePlatformService emailCampaignWritePlatformService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step updateEmailOutboundWithCampaignMessageStep() {
|
||||||
|
return steps.get(JobName.UPDATE_EMAIL_OUTBOUND_WITH_CAMPAIGN_MESSAGE.name())
|
||||||
|
.tasklet(updateEmailOutboundWithCampaignMessageTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job updateEmailOutboundWithCampaignMessageJob() {
|
||||||
|
return jobs.get(JobName.UPDATE_EMAIL_OUTBOUND_WITH_CAMPAIGN_MESSAGE.name()).start(updateEmailOutboundWithCampaignMessageStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UpdateEmailOutboundWithCampaignMessageTasklet updateEmailOutboundWithCampaignMessageTasklet() {
|
||||||
|
return new UpdateEmailOutboundWithCampaignMessageTasklet(emailCampaignReadPlatformService, emailCampaignRepository,
|
||||||
|
clientRepositoryWrapper, emailMessageRepository, emailCampaignWritePlatformService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.updateemailoutboundwithcampaignmessage;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.github.mustachejava.DefaultMustacheFactory;
|
||||||
|
import com.github.mustachejava.Mustache;
|
||||||
|
import com.github.mustachejava.MustacheFactory;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.mail.internet.AddressException;
|
||||||
|
import javax.mail.internet.InternetAddress;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.data.EmailCampaignData;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaign;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignRepository;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessage;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageRepository;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignNotFound;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.service.EmailCampaignReadPlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.email.service.EmailCampaignWritePlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.jobs.TenantDateTimeUtil;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpdateEmailOutboundWithCampaignMessageTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final EmailCampaignReadPlatformService emailCampaignReadPlatformService;
|
||||||
|
private final EmailCampaignRepository emailCampaignRepository;
|
||||||
|
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
||||||
|
private final EmailMessageRepository emailMessageRepository;
|
||||||
|
private final EmailCampaignWritePlatformService emailCampaignWritePlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
final Collection<EmailCampaignData> emailCampaignDataCollection = emailCampaignReadPlatformService
|
||||||
|
.retrieveAllScheduleActiveCampaign();
|
||||||
|
if (emailCampaignDataCollection != null) {
|
||||||
|
for (EmailCampaignData emailCampaignData : emailCampaignDataCollection) {
|
||||||
|
LocalDateTime tenantDateNow = TenantDateTimeUtil.tenantDateTime();
|
||||||
|
LocalDateTime nextTriggerDate = emailCampaignData.getNextTriggerDate().toLocalDateTime();
|
||||||
|
|
||||||
|
log.info("tenant time {} trigger time {}", tenantDateNow, nextTriggerDate);
|
||||||
|
if (nextTriggerDate.isBefore(tenantDateNow)) {
|
||||||
|
insertDirectCampaignIntoEmailOutboundTable(emailCampaignData.getParamValue(), emailCampaignData.getEmailSubject(),
|
||||||
|
emailCampaignData.getMessage(), emailCampaignData.getCampaignName(), emailCampaignData.getId());
|
||||||
|
updateTriggerDates(emailCampaignData.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertDirectCampaignIntoEmailOutboundTable(final String emailParams, final String emailSubject,
|
||||||
|
final String messageTemplate, final String campaignName, final Long campaignId) {
|
||||||
|
try {
|
||||||
|
HashMap<String, String> campaignParams = new ObjectMapper().readValue(emailParams, new TypeReference<>() {});
|
||||||
|
HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(emailParams, new TypeReference<>() {});
|
||||||
|
List<HashMap<String, Object>> runReportObject = emailCampaignWritePlatformService
|
||||||
|
.getRunReportByServiceImpl(campaignParams.get("reportName"), queryParamForRunReport);
|
||||||
|
if (runReportObject != null) {
|
||||||
|
for (HashMap<String, Object> entry : runReportObject) {
|
||||||
|
String message = compileEmailTemplate(messageTemplate, campaignName, entry);
|
||||||
|
Integer clientId = (Integer) entry.get("id");
|
||||||
|
EmailCampaign emailCampaign = emailCampaignRepository.findById(campaignId).orElse(null);
|
||||||
|
Client client = clientRepositoryWrapper.findOneWithNotFoundDetection(clientId.longValue());
|
||||||
|
String emailAddress = client.emailAddress();
|
||||||
|
|
||||||
|
if (emailAddress != null && isValidEmail(emailAddress)) {
|
||||||
|
EmailMessage emailMessage = EmailMessage.pendingEmail(null, client, null, emailCampaign, emailSubject, message,
|
||||||
|
emailAddress, campaignName);
|
||||||
|
emailMessageRepository.save(emailMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new EmailParamMappingException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTriggerDates(Long campaignId) {
|
||||||
|
final EmailCampaign emailCampaign = emailCampaignRepository.findById(campaignId)
|
||||||
|
.orElseThrow(() -> new EmailCampaignNotFound(campaignId));
|
||||||
|
LocalDateTime nextTriggerDate = emailCampaign.getNextTriggerDate();
|
||||||
|
emailCampaign.setLastTriggerDate(nextTriggerDate);
|
||||||
|
LocalDateTime newTriggerDateWithTime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(),
|
||||||
|
emailCampaign.getNextTriggerDate(), nextTriggerDate);
|
||||||
|
if (newTriggerDateWithTime.isBefore(DateUtils.getLocalDateTimeOfTenant())) {
|
||||||
|
newTriggerDateWithTime = CalendarUtils.getNextRecurringDate(emailCampaign.getRecurrence(), emailCampaign.getNextTriggerDate(),
|
||||||
|
DateUtils.getLocalDateTimeOfTenant());
|
||||||
|
}
|
||||||
|
|
||||||
|
emailCampaign.setNextTriggerDate(newTriggerDateWithTime);
|
||||||
|
emailCampaignRepository.saveAndFlush(emailCampaign);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String compileEmailTemplate(final String textMessageTemplate, final String campaignName,
|
||||||
|
final Map<String, Object> emailParams) {
|
||||||
|
final MustacheFactory mf = new DefaultMustacheFactory();
|
||||||
|
final Mustache mustache = mf.compile(new StringReader(textMessageTemplate), campaignName);
|
||||||
|
|
||||||
|
final StringWriter stringWriter = new StringWriter();
|
||||||
|
mustache.execute(stringWriter, emailParams);
|
||||||
|
|
||||||
|
return stringWriter.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidEmail(String email) {
|
||||||
|
boolean isValid = true;
|
||||||
|
try {
|
||||||
|
InternetAddress emailO = new InternetAddress(email);
|
||||||
|
emailO.validate();
|
||||||
|
} catch (AddressException ex) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.updatesmsoutboundwithcampaignmessage;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignWritePlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class UpdateSmsOutboundWithCampaignMessageConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private SmsCampaignRepository smsCampaignRepository;
|
||||||
|
@Autowired
|
||||||
|
private SmsCampaignWritePlatformService smsCampaignWritePlatformService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step updateSmsOutboundWithCampaignMessageStep() {
|
||||||
|
return steps.get(JobName.UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE.name()).tasklet(updateSmsOutboundWithCampaignMessageTasklet())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job updateSmsOutboundWithCampaignMessageJob() {
|
||||||
|
return jobs.get(JobName.UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE.name()).start(updateSmsOutboundWithCampaignMessageStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UpdateSmsOutboundWithCampaignMessageTasklet updateSmsOutboundWithCampaignMessageTasklet() {
|
||||||
|
return new UpdateSmsOutboundWithCampaignMessageTasklet(smsCampaignRepository, smsCampaignWritePlatformService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* 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.campaigns.jobs.updatesmsoutboundwithcampaignmessage;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collection;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.jobs.TenantDateTimeUtil;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignStatus;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignNotFound;
|
||||||
|
import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignWritePlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpdateSmsOutboundWithCampaignMessageTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final SmsCampaignRepository smsCampaignRepository;
|
||||||
|
private final SmsCampaignWritePlatformService smsCampaignWritePlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
final Collection<SmsCampaign> smsCampaignDataCollection = smsCampaignRepository
|
||||||
|
.findByTriggerTypeAndStatus(SmsCampaignTriggerType.SCHEDULE.getValue(), SmsCampaignStatus.ACTIVE.getValue());
|
||||||
|
if (!CollectionUtils.isEmpty(smsCampaignDataCollection)) {
|
||||||
|
for (SmsCampaign smsCampaign : smsCampaignDataCollection) {
|
||||||
|
LocalDateTime tenantDateNow = TenantDateTimeUtil.tenantDateTime();
|
||||||
|
LocalDateTime nextTriggerDate = smsCampaign.getNextTriggerDate();
|
||||||
|
|
||||||
|
log.info("tenant time {} trigger time {} {}", tenantDateNow, nextTriggerDate,
|
||||||
|
JobName.UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE.name());
|
||||||
|
if (nextTriggerDate.isBefore(tenantDateNow)) {
|
||||||
|
smsCampaignWritePlatformService.insertDirectCampaignIntoSmsOutboundTable(smsCampaign);
|
||||||
|
updateTriggerDates(smsCampaign.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTriggerDates(Long campaignId) {
|
||||||
|
final SmsCampaign smsCampaign = this.smsCampaignRepository.findById(campaignId)
|
||||||
|
.orElseThrow(() -> new SmsCampaignNotFound(campaignId));
|
||||||
|
LocalDateTime nextTriggerDate = smsCampaign.getNextTriggerDate();
|
||||||
|
smsCampaign.setLastTriggerDate(nextTriggerDate);
|
||||||
|
LocalDateTime nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getNextTriggerDate(),
|
||||||
|
nextTriggerDate);
|
||||||
|
if (nextRuntime.isBefore(DateUtils.getLocalDateTimeOfTenant())) {
|
||||||
|
nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getNextTriggerDate(),
|
||||||
|
DateUtils.getLocalDateTimeOfTenant());
|
||||||
|
}
|
||||||
|
|
||||||
|
smsCampaign.setNextTriggerDate(nextRuntime);
|
||||||
|
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
-59
@@ -38,6 +38,7 @@ import javax.ws.rs.QueryParam;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||||
@@ -57,13 +58,13 @@ import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
|||||||
import org.apache.fineract.infrastructure.core.service.Page;
|
import org.apache.fineract.infrastructure.core.service.Page;
|
||||||
import org.apache.fineract.infrastructure.core.service.SearchParameters;
|
import org.apache.fineract.infrastructure.core.service.SearchParameters;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Path("/smscampaigns")
|
@Path("/smscampaigns")
|
||||||
@Component
|
@Component
|
||||||
@Scope("singleton")
|
@Scope("singleton")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SmsCampaignApiResource {
|
public class SmsCampaignApiResource {
|
||||||
|
|
||||||
private final PlatformSecurityContext platformSecurityContext;
|
private final PlatformSecurityContext platformSecurityContext;
|
||||||
@@ -74,81 +75,80 @@ public class SmsCampaignApiResource {
|
|||||||
private final FromJsonHelper fromJsonHelper;
|
private final FromJsonHelper fromJsonHelper;
|
||||||
private final DefaultToApiJsonSerializer<CampaignPreviewData> previewCampaignMessageDefaultToApiJsonSerializer;
|
private final DefaultToApiJsonSerializer<CampaignPreviewData> previewCampaignMessageDefaultToApiJsonSerializer;
|
||||||
private final SmsCampaignWritePlatformService smsCampaignWritePlatformService;
|
private final SmsCampaignWritePlatformService smsCampaignWritePlatformService;
|
||||||
|
|
||||||
private final String resourceNameForPermissions = "SMS_CAMPAIGN";
|
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
|
|
||||||
@Autowired
|
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "SMS_CAMPAIGN";
|
||||||
public SmsCampaignApiResource(final PlatformSecurityContext platformSecurityContext,
|
|
||||||
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
|
|
||||||
final DefaultToApiJsonSerializer<SmsCampaignData> toApiJsonSerializer,
|
|
||||||
final SmsCampaignReadPlatformService smsCampaignReadPlatformService, final ApiRequestParameterHelper apiRequestParameterHelper,
|
|
||||||
final FromJsonHelper fromJsonHelper,
|
|
||||||
final DefaultToApiJsonSerializer<CampaignPreviewData> previewCampaignMessageDefaultToApiJsonSerializer,
|
|
||||||
final PlatformSecurityContext context, final SmsCampaignWritePlatformService smsCampaignWritePlatformService) {
|
|
||||||
this.platformSecurityContext = platformSecurityContext;
|
|
||||||
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
|
|
||||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
|
||||||
this.smsCampaignReadPlatformService = smsCampaignReadPlatformService;
|
|
||||||
this.apiRequestParameterHelper = apiRequestParameterHelper;
|
|
||||||
this.fromJsonHelper = fromJsonHelper;
|
|
||||||
this.previewCampaignMessageDefaultToApiJsonSerializer = previewCampaignMessageDefaultToApiJsonSerializer;
|
|
||||||
this.context = context;
|
|
||||||
this.smsCampaignWritePlatformService = smsCampaignWritePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("template")
|
@Path("template")
|
||||||
@Operation(summary = "Retrieve a SMS Campaign", description = "Example Requests:\n" + "\n" + "smscampaigns/1\n" + "\n" + "\n"
|
@Operation(summary = "Retrieve a SMS Campaign", description = """
|
||||||
+ "smscampaigns/1?template=true\n" + "\n" + "\n" + "smscampaigns/template")
|
Example Requests:
|
||||||
|
|
||||||
|
smscampaigns/1
|
||||||
|
|
||||||
|
|
||||||
|
smscampaigns/1?template=true
|
||||||
|
|
||||||
|
|
||||||
|
smscampaigns/template""")
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SmsCampaignData.class)))
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SmsCampaignData.class)))
|
||||||
public String template(@Context final UriInfo uriInfo) {
|
public String template(@Context final UriInfo uriInfo) {
|
||||||
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);
|
platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);
|
||||||
final SmsCampaignData smsCampaignData = this.smsCampaignReadPlatformService.retrieveTemplate(CampaignType.SMS.name());
|
final SmsCampaignData smsCampaignData = smsCampaignReadPlatformService.retrieveTemplate(CampaignType.SMS.name());
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, smsCampaignData);
|
return toApiJsonSerializer.serialize(settings, smsCampaignData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
@Operation(summary = "Create a SMS Campaign", description = "Mandatory Fields\n"
|
@Operation(summary = "Create a SMS Campaign", description = """
|
||||||
+ "campaignName, campaignType, triggerType, providerId, runReportId, message\n" + "\n"
|
Mandatory Fields
|
||||||
+ "Mandatory Fields for Cash based on selected report id\n" + "paramValue in json format")
|
campaignName, campaignType, triggerType, providerId, runReportId, message
|
||||||
|
|
||||||
|
Mandatory Fields for Cash based on selected report id
|
||||||
|
paramValue in json format""")
|
||||||
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = CommandWrapper.class)))
|
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = CommandWrapper.class)))
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CommandProcessingResult.class)))
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CommandProcessingResult.class)))
|
||||||
public String createCampaign(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
public String createCampaign(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
this.platformSecurityContext.authenticatedUser();
|
platformSecurityContext.authenticatedUser();
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createSmsCampaign().withJson(apiRequestBodyAsJson).build();
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().createSmsCampaign().withJson(apiRequestBodyAsJson).build();
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{resourceId}")
|
@Path("{resourceId}")
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
@Operation(summary = "Retrieve a SMS Campaign", description = "Example Requests:\n" + "\n" + "smscampaigns/1\n")
|
@Operation(summary = "Retrieve a SMS Campaign", description = """
|
||||||
|
Example Requests:
|
||||||
|
|
||||||
|
smscampaigns/1
|
||||||
|
""")
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SmsCampaignData.class)))
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SmsCampaignData.class)))
|
||||||
public String retrieveCampaign(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
|
public String retrieveCampaign(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
|
||||||
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);
|
platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);
|
||||||
SmsCampaignData smsCampaignData = this.smsCampaignReadPlatformService.retrieveOne(resourceId);
|
SmsCampaignData smsCampaignData = smsCampaignReadPlatformService.retrieveOne(resourceId);
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, smsCampaignData);
|
return toApiJsonSerializer.serialize(settings, smsCampaignData);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
@Operation(summary = "List SMS Campaigns", description = "Example Requests:\n" + "\n" + "smscampaigns")
|
@Operation(summary = "List SMS Campaigns", description = """
|
||||||
|
Example Requests:
|
||||||
|
|
||||||
|
smscampaigns""")
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SmsCampaignData.class)))
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SmsCampaignData.class)))
|
||||||
public String retrieveAllEmails(@QueryParam("sqlSearch") final String sqlSearch, @QueryParam("offset") final Integer offset,
|
public String retrieveAllEmails(@QueryParam("sqlSearch") final String sqlSearch, @QueryParam("offset") final Integer offset,
|
||||||
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
||||||
@QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
|
@QueryParam("sortOrder") final String sortOrder, @Context final UriInfo uriInfo) {
|
||||||
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);
|
platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);
|
||||||
final SearchParameters searchParameters = SearchParameters.forSMSCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
|
final SearchParameters searchParameters = SearchParameters.forSMSCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
|
||||||
Page<SmsCampaignData> smsCampaignDataCollection = this.smsCampaignReadPlatformService.retrieveAll(searchParameters);
|
Page<SmsCampaignData> smsCampaignDataCollection = smsCampaignReadPlatformService.retrieveAll(searchParameters);
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, smsCampaignDataCollection);
|
return toApiJsonSerializer.serialize(settings, smsCampaignDataCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
@@ -162,8 +162,8 @@ public class SmsCampaignApiResource {
|
|||||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSmsCampaign(campaignId).withJson(apiRequestBodyAsJson)
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSmsCampaign(campaignId).withJson(apiRequestBodyAsJson)
|
||||||
.build();
|
.build();
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -176,18 +176,18 @@ public class SmsCampaignApiResource {
|
|||||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
|
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
|
||||||
CommandProcessingResult result = null;
|
CommandProcessingResult result = null;
|
||||||
CommandWrapper commandRequest = null;
|
CommandWrapper commandRequest;
|
||||||
if (is(commandParam, "activate")) {
|
if (is(commandParam, "activate")) {
|
||||||
commandRequest = builder.activateSmsCampaign(campaignId).build();
|
commandRequest = builder.activateSmsCampaign(campaignId).build();
|
||||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
} else if (is(commandParam, "close")) {
|
} else if (is(commandParam, "close")) {
|
||||||
commandRequest = builder.closeSmsCampaign(campaignId).build();
|
commandRequest = builder.closeSmsCampaign(campaignId).build();
|
||||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
} else if (is(commandParam, "reactivate")) {
|
} else if (is(commandParam, "reactivate")) {
|
||||||
commandRequest = builder.reactivateSmsCampaign(campaignId).build();
|
commandRequest = builder.reactivateSmsCampaign(campaignId).build();
|
||||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
}
|
}
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -195,14 +195,14 @@ public class SmsCampaignApiResource {
|
|||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
public String preview(final String apiRequestBodyAsJson, @Context final UriInfo uriInfo) {
|
public String preview(final String apiRequestBodyAsJson, @Context final UriInfo uriInfo) {
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
CampaignPreviewData campaignMessage = null;
|
CampaignPreviewData campaignMessage;
|
||||||
final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
|
final JsonElement parsedQuery = fromJsonHelper.parse(apiRequestBodyAsJson);
|
||||||
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
|
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, fromJsonHelper);
|
||||||
campaignMessage = this.smsCampaignWritePlatformService.previewMessage(query);
|
campaignMessage = smsCampaignWritePlatformService.previewMessage(query);
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.previewCampaignMessageDefaultToApiJsonSerializer.serialize(settings, campaignMessage, new HashSet<String>());
|
return previewCampaignMessageDefaultToApiJsonSerializer.serialize(settings, campaignMessage, new HashSet<>());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,8 +212,8 @@ public class SmsCampaignApiResource {
|
|||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CommandProcessingResult.class)))
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CommandProcessingResult.class)))
|
||||||
public String delete(@PathParam("campaignId") final Long campaignId) {
|
public String delete(@PathParam("campaignId") final Long campaignId) {
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSmsCampaign(campaignId).build();
|
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSmsCampaign(campaignId).build();
|
||||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean is(final String commandParam, final String commandValue) {
|
private boolean is(final String commandParam, final String commandValue) {
|
||||||
|
|||||||
+1
-1
@@ -167,7 +167,7 @@ public class SmsCampaign extends AbstractPersistableCustom {
|
|||||||
LocalDateTime recurrenceStartDate = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant());
|
LocalDateTime recurrenceStartDate = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
if (SmsCampaignTriggerType.fromInt(triggerType.intValue()).isSchedule()) {
|
if (SmsCampaignTriggerType.fromInt(triggerType.intValue()).isSchedule()) {
|
||||||
final Locale locale = command.extractLocale();
|
final Locale locale = command.extractLocale();
|
||||||
String dateTimeFormat = null;
|
String dateTimeFormat;
|
||||||
if (command.hasParameter(SmsCampaignValidator.dateTimeFormat)) {
|
if (command.hasParameter(SmsCampaignValidator.dateTimeFormat)) {
|
||||||
dateTimeFormat = command.stringValueOfParameterNamed(SmsCampaignValidator.dateTimeFormat);
|
dateTimeFormat = command.stringValueOfParameterNamed(SmsCampaignValidator.dateTimeFormat);
|
||||||
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(dateTimeFormat).withLocale(locale);
|
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(dateTimeFormat).withLocale(locale);
|
||||||
|
|||||||
+2
-3
@@ -24,7 +24,6 @@ import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
|
|||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonQuery;
|
import org.apache.fineract.infrastructure.core.api.JsonQuery;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||||
@@ -43,14 +42,14 @@ public interface SmsCampaignWritePlatformService {
|
|||||||
|
|
||||||
CommandProcessingResult reactivateSmsCampaign(Long campaignId, JsonCommand command);
|
CommandProcessingResult reactivateSmsCampaign(Long campaignId, JsonCommand command);
|
||||||
|
|
||||||
|
void insertDirectCampaignIntoSmsOutboundTable(SmsCampaign smsCampaign);
|
||||||
|
|
||||||
void insertDirectCampaignIntoSmsOutboundTable(Loan loan, SmsCampaign smsCampaign);
|
void insertDirectCampaignIntoSmsOutboundTable(Loan loan, SmsCampaign smsCampaign);
|
||||||
|
|
||||||
String compileSmsTemplate(String textMessageTemplate, String campaignName, Map<String, Object> smsParams);
|
String compileSmsTemplate(String textMessageTemplate, String campaignName, Map<String, Object> smsParams);
|
||||||
|
|
||||||
CampaignPreviewData previewMessage(JsonQuery query);
|
CampaignPreviewData previewMessage(JsonQuery query);
|
||||||
|
|
||||||
void storeTemplateMessageIntoSmsOutBoundTable() throws JobExecutionException;
|
|
||||||
|
|
||||||
void insertDirectCampaignIntoSmsOutboundTable(Client client, SmsCampaign smsCampaign);
|
void insertDirectCampaignIntoSmsOutboundTable(Client client, SmsCampaign smsCampaign);
|
||||||
|
|
||||||
void insertDirectCampaignIntoSmsOutboundTable(SavingsAccount savingsAccount, SmsCampaign smsCampaign);
|
void insertDirectCampaignIntoSmsOutboundTable(SavingsAccount savingsAccount, SmsCampaign smsCampaign);
|
||||||
|
|||||||
+28
-147
@@ -41,8 +41,8 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignStatus;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.campaigns.sms.data.CampaignPreviewData;
|
import org.apache.fineract.infrastructure.campaigns.sms.data.CampaignPreviewData;
|
||||||
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
|
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
|
||||||
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository;
|
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository;
|
||||||
@@ -66,10 +66,6 @@ import org.apache.fineract.infrastructure.dataqueries.domain.ReportRepository;
|
|||||||
import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException;
|
import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
|
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
||||||
import org.apache.fineract.infrastructure.gcm.domain.DeviceRegistrationRepositoryWrapper;
|
|
||||||
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.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
|
||||||
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
||||||
@@ -84,19 +80,16 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTypeException;
|
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTypeException;
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWritePlatformService {
|
public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWritePlatformService {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SmsCampaignWritePlatformServiceJpaImpl.class);
|
|
||||||
|
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
|
|
||||||
private final SmsCampaignRepository smsCampaignRepository;
|
private final SmsCampaignRepository smsCampaignRepository;
|
||||||
@@ -108,32 +101,9 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
|||||||
private final ReadReportingService readReportingService;
|
private final ReadReportingService readReportingService;
|
||||||
private final GenericDataService genericDataService;
|
private final GenericDataService genericDataService;
|
||||||
private final FromJsonHelper fromJsonHelper;
|
private final FromJsonHelper fromJsonHelper;
|
||||||
private final DeviceRegistrationRepositoryWrapper deviceRegistrationRepository;
|
|
||||||
|
|
||||||
private final SmsMessageScheduledJobService smsMessageScheduledJobService;
|
private final SmsMessageScheduledJobService smsMessageScheduledJobService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public SmsCampaignWritePlatformServiceJpaImpl(final PlatformSecurityContext context, final SmsCampaignRepository smsCampaignRepository,
|
|
||||||
final SmsCampaignValidator smsCampaignValidator, final ReportRepository reportRepository,
|
|
||||||
final SmsMessageRepository smsMessageRepository, final ClientRepositoryWrapper clientRepositoryWrapper,
|
|
||||||
final ReadReportingService readReportingService, final GenericDataService genericDataService,
|
|
||||||
final FromJsonHelper fromJsonHelper, final GroupRepository groupRepository,
|
|
||||||
final SmsMessageScheduledJobService smsMessageScheduledJobService,
|
|
||||||
final DeviceRegistrationRepositoryWrapper deviceRegistrationRepository) {
|
|
||||||
this.context = context;
|
|
||||||
this.smsCampaignRepository = smsCampaignRepository;
|
|
||||||
this.smsCampaignValidator = smsCampaignValidator;
|
|
||||||
this.reportRepository = reportRepository;
|
|
||||||
this.smsMessageRepository = smsMessageRepository;
|
|
||||||
this.clientRepositoryWrapper = clientRepositoryWrapper;
|
|
||||||
this.readReportingService = readReportingService;
|
|
||||||
this.genericDataService = genericDataService;
|
|
||||||
this.fromJsonHelper = fromJsonHelper;
|
|
||||||
this.groupRepository = groupRepository;
|
|
||||||
this.smsMessageScheduledJobService = smsMessageScheduledJobService;
|
|
||||||
this.deviceRegistrationRepository = deviceRegistrationRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult create(JsonCommand command) {
|
public CommandProcessingResult create(JsonCommand command) {
|
||||||
@@ -150,10 +120,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
|||||||
}
|
}
|
||||||
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(smsCampaign.getId()).build();
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.withEntityId(smsCampaign.getId()) //
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -210,45 +177,41 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
|||||||
smsCampaign.delete();
|
smsCampaign.delete();
|
||||||
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder().withEntityId(smsCampaign.getId()).build();
|
||||||
.withEntityId(smsCampaign.getId()) //
|
|
||||||
.build();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertDirectCampaignIntoSmsOutboundTable(SmsCampaign smsCampaign) {
|
@Override
|
||||||
|
public void insertDirectCampaignIntoSmsOutboundTable(SmsCampaign smsCampaign) {
|
||||||
try {
|
try {
|
||||||
HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
|
HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(), new TypeReference<>() {});
|
||||||
new TypeReference<HashMap<String, String>>() {});
|
|
||||||
|
|
||||||
HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(),
|
HashMap<String, String> queryParamForRunReport = new ObjectMapper().readValue(smsCampaign.getParamValue(),
|
||||||
new TypeReference<HashMap<String, String>>() {});
|
new TypeReference<>() {});
|
||||||
|
|
||||||
List<HashMap<String, Object>> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"),
|
List<HashMap<String, Object>> runReportObject = getRunReportByServiceImpl(campaignParams.get("reportName"),
|
||||||
queryParamForRunReport);
|
queryParamForRunReport);
|
||||||
|
|
||||||
if (runReportObject != null) {
|
if (runReportObject != null) {
|
||||||
for (HashMap<String, Object> entry : runReportObject) {
|
for (HashMap<String, Object> entry : runReportObject) {
|
||||||
String textMessage = this.compileSmsTemplate(smsCampaign.getMessage(), smsCampaign.getCampaignName(), entry);
|
String textMessage = compileSmsTemplate(smsCampaign.getMessage(), smsCampaign.getCampaignName(), entry);
|
||||||
Integer clientId = (Integer) entry.get("id");
|
Integer clientId = (Integer) entry.get("id");
|
||||||
Object mobileNo = entry.get("mobileNo");
|
Object mobileNo = entry.get("mobileNo");
|
||||||
|
|
||||||
Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId.longValue());
|
Client client = clientRepositoryWrapper.findOneWithNotFoundDetection(clientId.longValue());
|
||||||
if (this.smsCampaignValidator.isValidNotificationOrSms(client, smsCampaign, mobileNo)) {
|
if (smsCampaignValidator.isValidNotificationOrSms(client, smsCampaign, mobileNo)) {
|
||||||
// String countryCode =
|
|
||||||
// this.smsReadPlatformService.retrieveCountryCode(client.getOffice().getId()).getCountryCode();
|
|
||||||
String mobileNumber = null;
|
String mobileNumber = null;
|
||||||
if (mobileNo != null) {
|
if (mobileNo != null) {
|
||||||
mobileNumber = mobileNo.toString();
|
mobileNumber = mobileNo.toString();
|
||||||
}
|
}
|
||||||
SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, mobileNumber, smsCampaign,
|
SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, mobileNumber, smsCampaign,
|
||||||
smsCampaign.isNotification());
|
smsCampaign.isNotification());
|
||||||
this.smsMessageRepository.save(smsMessage);
|
smsMessageRepository.save(smsMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
LOG.error("Error occured.", e);
|
log.error("Error occurred.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -309,10 +272,8 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final IOException e) {
|
} catch (final IOException | RuntimeException e) {
|
||||||
LOG.error("Error occured.", e);
|
log.error("Error occured.", e);
|
||||||
} catch (final RuntimeException e) {
|
|
||||||
LOG.error("Error occured.", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,10 +314,8 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final IOException e) {
|
} catch (final IOException | RuntimeException e) {
|
||||||
LOG.error("Error occured.", e);
|
log.error("Error occured.", e);
|
||||||
} catch (final RuntimeException e) {
|
|
||||||
LOG.error("Error occured.", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,52 +355,19 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final IOException e) {
|
} catch (final IOException | RuntimeException e) {
|
||||||
LOG.error("Error occured.", e);
|
log.error("Error occured.", e);
|
||||||
} catch (final RuntimeException e) {
|
|
||||||
LOG.error("Error occured.", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTriggerDates(Long campaignId) {
|
|
||||||
final SmsCampaign smsCampaign = this.smsCampaignRepository.findById(campaignId)
|
|
||||||
.orElseThrow(() -> new SmsCampaignNotFound(campaignId));
|
|
||||||
LocalDateTime nextTriggerDate = smsCampaign.getNextTriggerDate();
|
|
||||||
smsCampaign.setLastTriggerDate(nextTriggerDate);
|
|
||||||
// calculate new trigger date and insert into next trigger date
|
|
||||||
|
|
||||||
/**
|
|
||||||
* next run time has to be in the future if not calculate a new future date
|
|
||||||
*/
|
|
||||||
LocalDateTime nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getNextTriggerDate(),
|
|
||||||
nextTriggerDate);
|
|
||||||
if (nextRuntime.isBefore(DateUtils.getLocalDateTimeOfTenant())) { // means
|
|
||||||
// next
|
|
||||||
// run
|
|
||||||
// time is
|
|
||||||
// in the
|
|
||||||
// past
|
|
||||||
// calculate
|
|
||||||
// a new
|
|
||||||
// future
|
|
||||||
// date
|
|
||||||
nextRuntime = CalendarUtils.getNextRecurringDate(smsCampaign.getRecurrence(), smsCampaign.getNextTriggerDate(),
|
|
||||||
DateUtils.getLocalDateTimeOfTenant());
|
|
||||||
}
|
|
||||||
|
|
||||||
smsCampaign.setNextTriggerDate(nextRuntime);
|
|
||||||
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult activateSmsCampaign(Long campaignId, JsonCommand command) {
|
public CommandProcessingResult activateSmsCampaign(Long campaignId, JsonCommand command) {
|
||||||
final AppUser currentUser = this.context.authenticatedUser();
|
final AppUser currentUser = context.authenticatedUser();
|
||||||
|
|
||||||
this.smsCampaignValidator.validateActivation(command.json());
|
this.smsCampaignValidator.validateActivation(command.json());
|
||||||
|
|
||||||
final SmsCampaign smsCampaign = this.smsCampaignRepository.findById(campaignId)
|
final SmsCampaign smsCampaign = smsCampaignRepository.findById(campaignId).orElseThrow(() -> new SmsCampaignNotFound(campaignId));
|
||||||
.orElseThrow(() -> new SmsCampaignNotFound(campaignId));
|
|
||||||
|
|
||||||
final Locale locale = command.extractLocale();
|
final Locale locale = command.extractLocale();
|
||||||
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
|
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
|
||||||
@@ -449,7 +375,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
|||||||
|
|
||||||
smsCampaign.activate(currentUser, fmt, activationDate);
|
smsCampaign.activate(currentUser, fmt, activationDate);
|
||||||
|
|
||||||
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
smsCampaignRepository.saveAndFlush(smsCampaign);
|
||||||
|
|
||||||
if (smsCampaign.isDirect()) {
|
if (smsCampaign.isDirect()) {
|
||||||
insertDirectCampaignIntoSmsOutboundTable(smsCampaign);
|
insertDirectCampaignIntoSmsOutboundTable(smsCampaign);
|
||||||
@@ -475,10 +401,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
|||||||
* if campaign is direct insert campaign message into sms outbound table else if its a schedule create a job
|
* if campaign is direct insert campaign message into sms outbound table else if its a schedule create a job
|
||||||
* process for it
|
* process for it
|
||||||
*/
|
*/
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(smsCampaign.getId()).build();
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.withEntityId(smsCampaign.getId()) //
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -529,7 +452,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
|||||||
final String response = this.genericDataService.generateJsonFromGenericResultsetData(results);
|
final String response = this.genericDataService.generateJsonFromGenericResultsetData(results);
|
||||||
resultList = new ObjectMapper().readValue(response, new TypeReference<List<HashMap<String, Object>>>() {});
|
resultList = new ObjectMapper().readValue(response, new TypeReference<List<HashMap<String, Object>>>() {});
|
||||||
} catch (JsonParseException e) {
|
} catch (JsonParseException e) {
|
||||||
LOG.info("Conversion of report query results to JSON failed", e);
|
log.info("Conversion of report query results to JSON failed", e);
|
||||||
return resultList;
|
return resultList;
|
||||||
}
|
}
|
||||||
// loop changes array date to string date
|
// loop changes array date to string date
|
||||||
@@ -628,30 +551,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
|||||||
}
|
}
|
||||||
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder().withEntityId(smsCampaign.getId()).build();
|
||||||
.withEntityId(smsCampaign.getId()) //
|
|
||||||
.build();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE)
|
|
||||||
public void storeTemplateMessageIntoSmsOutBoundTable() throws JobExecutionException {
|
|
||||||
final Collection<SmsCampaign> smsCampaignDataCollection = this.smsCampaignRepository
|
|
||||||
.findByTriggerTypeAndStatus(SmsCampaignTriggerType.SCHEDULE.getValue(), SmsCampaignStatus.ACTIVE.getValue());
|
|
||||||
if (smsCampaignDataCollection != null) {
|
|
||||||
for (SmsCampaign smsCampaign : smsCampaignDataCollection) {
|
|
||||||
LocalDateTime tenantDateNow = tenantDateTime();
|
|
||||||
LocalDateTime nextTriggerDate = smsCampaign.getNextTriggerDate();
|
|
||||||
|
|
||||||
LOG.info("tenant time {} trigger time {} {}", tenantDateNow, nextTriggerDate,
|
|
||||||
JobName.UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE.name());
|
|
||||||
if (nextTriggerDate.isBefore(tenantDateNow)) {
|
|
||||||
insertDirectCampaignIntoSmsOutboundTable(smsCampaign);
|
|
||||||
this.updateTriggerDates(smsCampaign.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause) {
|
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause) {
|
||||||
@@ -672,23 +572,4 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
|||||||
}
|
}
|
||||||
return today;
|
return today;
|
||||||
}
|
}
|
||||||
|
|
||||||
// private String formatDestinationPhoneNumber(String phoneNumber, String
|
|
||||||
// countryCallingCode) {
|
|
||||||
// StringBuilder formatedPhoneNumber = new StringBuilder("+");
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// Long phoneNumberToLong = Long.parseLong(phoneNumber);
|
|
||||||
// Long countryCallingCodeToLong = Long.parseLong(countryCallingCode);
|
|
||||||
// formatedPhoneNumber.append(Long.toString(countryCallingCodeToLong));
|
|
||||||
// formatedPhoneNumber.append(Long.toString(phoneNumberToLong));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// catch (Exception e) {
|
|
||||||
// LOG.error("Invalid phone number or country calling code, must contain
|
|
||||||
// only numbers", e);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return formatedPhoneNumber.toString();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
-10
@@ -23,7 +23,6 @@ import java.util.Map;
|
|||||||
import org.apache.fineract.infrastructure.core.auditing.JpaAuditingHandlerRegistrar;
|
import org.apache.fineract.infrastructure.core.auditing.JpaAuditingHandlerRegistrar;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AuditorAwareImpl;
|
import org.apache.fineract.infrastructure.core.domain.AuditorAwareImpl;
|
||||||
import org.apache.fineract.infrastructure.core.persistence.DatabaseSelectingPersistenceUnitPostProcessor;
|
import org.apache.fineract.infrastructure.core.persistence.DatabaseSelectingPersistenceUnitPostProcessor;
|
||||||
import org.apache.fineract.infrastructure.core.persistence.ExtendedJpaTransactionManager;
|
|
||||||
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
|
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
|
||||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
|
||||||
import org.eclipse.persistence.config.PersistenceUnitProperties;
|
import org.eclipse.persistence.config.PersistenceUnitProperties;
|
||||||
@@ -31,7 +30,6 @@ import org.springframework.beans.factory.ObjectProvider;
|
|||||||
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
|
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
|
||||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
|
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||||
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
|
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
|
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -102,14 +100,6 @@ public class JPAConfig extends JpaBaseConfiguration {
|
|||||||
return new AuditorAwareImpl();
|
return new AuditorAwareImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@Bean
|
|
||||||
public PlatformTransactionManager transactionManager(ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
|
|
||||||
ExtendedJpaTransactionManager transactionManager = new ExtendedJpaTransactionManager();
|
|
||||||
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
|
|
||||||
return transactionManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public TransactionTemplate txTemplate(PlatformTransactionManager transactionManager) {
|
public TransactionTemplate txTemplate(PlatformTransactionManager transactionManager) {
|
||||||
TransactionTemplate tt = new TransactionTemplate();
|
TransactionTemplate tt = new TransactionTemplate();
|
||||||
|
|||||||
+2
@@ -22,6 +22,7 @@ import java.sql.Connection;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.jdbc.datasource.AbstractDataSource;
|
import org.springframework.jdbc.datasource.AbstractDataSource;
|
||||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -36,6 +37,7 @@ import org.springframework.stereotype.Service;
|
|||||||
* of this request.
|
* of this request.
|
||||||
*/
|
*/
|
||||||
@Service(value = "routingDataSource")
|
@Service(value = "routingDataSource")
|
||||||
|
@Primary
|
||||||
public class RoutingDataSource extends AbstractDataSource {
|
public class RoutingDataSource extends AbstractDataSource {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
+13
-30
@@ -20,10 +20,10 @@ package org.apache.fineract.infrastructure.gcm.service;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.configuration.service.ExternalServicesPropertiesReadPlatformService;
|
import org.apache.fineract.infrastructure.configuration.service.ExternalServicesPropertiesReadPlatformService;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.gcm.GcmConstants;
|
import org.apache.fineract.infrastructure.gcm.GcmConstants;
|
||||||
@@ -38,29 +38,20 @@ import org.apache.fineract.infrastructure.gcm.domain.Sender;
|
|||||||
import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
|
||||||
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
||||||
import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class NotificationSenderService {
|
public class NotificationSenderService {
|
||||||
|
|
||||||
private final DeviceRegistrationRepositoryWrapper deviceRegistrationRepositoryWrapper;
|
private final DeviceRegistrationRepositoryWrapper deviceRegistrationRepositoryWrapper;
|
||||||
private final SmsMessageRepository smsMessageRepository;
|
private final SmsMessageRepository smsMessageRepository;
|
||||||
private ExternalServicesPropertiesReadPlatformService propertiesReadPlatformService;
|
private final ExternalServicesPropertiesReadPlatformService propertiesReadPlatformService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public NotificationSenderService(final DeviceRegistrationRepositoryWrapper deviceRegistrationRepositoryWrapper,
|
|
||||||
final SmsMessageRepository smsMessageRepository,
|
|
||||||
final ExternalServicesPropertiesReadPlatformService propertiesReadPlatformService) {
|
|
||||||
this.deviceRegistrationRepositoryWrapper = deviceRegistrationRepositoryWrapper;
|
|
||||||
this.smsMessageRepository = smsMessageRepository;
|
|
||||||
this.propertiesReadPlatformService = propertiesReadPlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendNotification(List<SmsMessage> smsMessages) {
|
public void sendNotification(List<SmsMessage> smsMessages) {
|
||||||
Map<Long, List<SmsMessage>> notificationByEachClient = getNotificationListByClient(smsMessages);
|
Map<Long, List<SmsMessage>> notificationByEachClient = getNotificationListByClient(smsMessages);
|
||||||
for (Map.Entry<Long, List<SmsMessage>> entry : notificationByEachClient.entrySet()) {
|
for (Map.Entry<Long, List<SmsMessage>> entry : notificationByEachClient.entrySet()) {
|
||||||
this.sendNotifiaction(entry.getKey(), entry.getValue());
|
sendNotification(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +63,7 @@ public class NotificationSenderService {
|
|||||||
if (notificationByEachClient.containsKey(clientId)) {
|
if (notificationByEachClient.containsKey(clientId)) {
|
||||||
notificationByEachClient.get(clientId).add(smsMessage);
|
notificationByEachClient.get(clientId).add(smsMessage);
|
||||||
} else {
|
} else {
|
||||||
List<SmsMessage> msgList = new ArrayList<>(Arrays.asList(smsMessage));
|
List<SmsMessage> msgList = new ArrayList<>(List.of(smsMessage));
|
||||||
notificationByEachClient.put(clientId, msgList);
|
notificationByEachClient.put(clientId, msgList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,10 +72,10 @@ public class NotificationSenderService {
|
|||||||
return notificationByEachClient;
|
return notificationByEachClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendNotifiaction(Long clientId, List<SmsMessage> smsList) {
|
public void sendNotification(Long clientId, List<SmsMessage> smsList) {
|
||||||
|
|
||||||
DeviceRegistration deviceRegistration = this.deviceRegistrationRepositoryWrapper.findDeviceRegistrationByClientId(clientId);
|
DeviceRegistration deviceRegistration = deviceRegistrationRepositoryWrapper.findDeviceRegistrationByClientId(clientId);
|
||||||
NotificationConfigurationData notificationConfigurationData = this.propertiesReadPlatformService.getNotificationConfiguration();
|
NotificationConfigurationData notificationConfigurationData = propertiesReadPlatformService.getNotificationConfiguration();
|
||||||
String registrationId = null;
|
String registrationId = null;
|
||||||
if (deviceRegistration != null) {
|
if (deviceRegistration != null) {
|
||||||
registrationId = deviceRegistration.getRegistrationId();
|
registrationId = deviceRegistration.getRegistrationId();
|
||||||
@@ -93,18 +84,10 @@ public class NotificationSenderService {
|
|||||||
try {
|
try {
|
||||||
Notification notification = new Notification.Builder(GcmConstants.defaultIcon).title(GcmConstants.title)
|
Notification notification = new Notification.Builder(GcmConstants.defaultIcon).title(GcmConstants.title)
|
||||||
.body(smsMessage.getMessage()).build();
|
.body(smsMessage.getMessage()).build();
|
||||||
Message.Builder b = new Message.Builder();
|
Message message = new Message.Builder().notification(notification).dryRun(false).contentAvailable(true)
|
||||||
b.notification(notification);
|
.timeToLive(GcmConstants.TIME_TO_LIVE).priority(Priority.HIGH).delayWhileIdle(true).build();
|
||||||
b.dryRun(false);
|
Sender sender = new Sender(notificationConfigurationData.getServerKey(), notificationConfigurationData.getFcmEndPoint());
|
||||||
b.contentAvailable(true);
|
Result res = sender.send(message, registrationId, 3);
|
||||||
b.timeToLive(GcmConstants.TIME_TO_LIVE);
|
|
||||||
b.priority(Priority.HIGH);
|
|
||||||
b.delayWhileIdle(true);
|
|
||||||
Message msg = b.build();
|
|
||||||
Sender s = new Sender(notificationConfigurationData.getServerKey(), notificationConfigurationData.getFcmEndPoint());
|
|
||||||
Result res;
|
|
||||||
|
|
||||||
res = s.send(msg, registrationId, 3);
|
|
||||||
if (res.getSuccess() != null && res.getSuccess() > 0) {
|
if (res.getSuccess() != null && res.getSuccess() > 0) {
|
||||||
smsMessage.setStatusType(SmsMessageStatusType.SENT.getValue());
|
smsMessage.setStatusType(SmsMessageStatusType.SENT.getValue());
|
||||||
smsMessage.setDeliveredOnDate(DateUtils.getLocalDateTimeOfTenant());
|
smsMessage.setDeliveredOnDate(DateUtils.getLocalDateTimeOfTenant());
|
||||||
@@ -116,7 +99,7 @@ public class NotificationSenderService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.smsMessageRepository.saveAll(smsList);
|
smsMessageRepository.saveAll(smsList);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.persistence.ExtendedJpaTransactionManager;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
|
||||||
|
import org.springframework.batch.core.configuration.JobRegistry;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.BatchConfigurer;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||||
|
import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor;
|
||||||
|
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
|
||||||
|
import org.springframework.batch.core.repository.JobRepository;
|
||||||
|
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableBatchProcessing
|
||||||
|
public class ScheduledJobRunnerConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PlatformTransactionManager transactionManager(ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
|
||||||
|
ExtendedJpaTransactionManager transactionManager = new ExtendedJpaTransactionManager();
|
||||||
|
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
|
||||||
|
return transactionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public BatchConfigurer batchConfigurer(RoutingDataSource routingDataSource, PlatformTransactionManager platformTransactionManager) {
|
||||||
|
return new DefaultBatchConfigurer(routingDataSource) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlatformTransactionManager getTransactionManager() {
|
||||||
|
return platformTransactionManager;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JobRepositoryFactoryBean jobRepositoryFactoryBean(RoutingDataSource routingDataSource,
|
||||||
|
PlatformTransactionManager transactionManager) throws Exception {
|
||||||
|
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
|
||||||
|
factory.setDataSource(routingDataSource);
|
||||||
|
factory.setTransactionManager(transactionManager);
|
||||||
|
factory.setIsolationLevelForCreate("ISOLATION_REPEATABLE_READ");
|
||||||
|
factory.afterPropertiesSet();
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JobRepository jobRepository(JobRepositoryFactoryBean factory) throws Exception {
|
||||||
|
return factory.getObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SimpleJobLauncher jobLauncher(JobRepository jobRepository) throws Exception {
|
||||||
|
SimpleJobLauncher launcher = new SimpleJobLauncher();
|
||||||
|
launcher.setJobRepository(jobRepository);
|
||||||
|
launcher.afterPropertiesSet();
|
||||||
|
return launcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
|
||||||
|
final JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();
|
||||||
|
postProcessor.setJobRegistry(jobRegistry);
|
||||||
|
return postProcessor;
|
||||||
|
}
|
||||||
|
}
|
||||||
-104
@@ -1,104 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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.annotation;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
|
||||||
import org.springframework.core.env.StandardEnvironment;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
|
||||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
|
||||||
import org.springframework.core.type.MethodMetadata;
|
|
||||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
|
||||||
import org.springframework.core.type.classreading.MetadataReader;
|
|
||||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parser to find method which is marked with CronTargetMethod annotation
|
|
||||||
*/
|
|
||||||
public final class CronMethodParser {
|
|
||||||
|
|
||||||
private CronMethodParser() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ClassMethodNamesPair {
|
|
||||||
|
|
||||||
public String className;
|
|
||||||
public String methodName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String SEARCH_PACKAGE = "org.apache.fineract.";
|
|
||||||
|
|
||||||
private static final String CRON_ANNOTATION_ATTRIBUTE_NAME = "jobName";
|
|
||||||
|
|
||||||
private static final String RESOURCE_PATTERN = "**/*.class";
|
|
||||||
|
|
||||||
private static final Map<String, ClassMethodNamesPair> targetMethosMap = new HashMap<>();
|
|
||||||
|
|
||||||
private static final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
|
|
||||||
|
|
||||||
private static final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
|
|
||||||
|
|
||||||
public static ClassMethodNamesPair findTargetMethodDetails(final String attributeValue) throws IOException {
|
|
||||||
if (!targetMethosMap.containsKey(attributeValue)) {
|
|
||||||
findAnnotationMethods(CronTarget.class, CRON_ANNOTATION_ATTRIBUTE_NAME);
|
|
||||||
}
|
|
||||||
return targetMethosMap.get(attributeValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* method adds all the method names to map with annotation attribute value as key
|
|
||||||
*/
|
|
||||||
private static void findAnnotationMethods(final Class<? extends Annotation> annotationClass, final String attributeName)
|
|
||||||
throws IOException {
|
|
||||||
final String basePackagePath = ClassUtils
|
|
||||||
.convertClassNameToResourcePath(new StandardEnvironment().resolveRequiredPlaceholders(SEARCH_PACKAGE));
|
|
||||||
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + basePackagePath + "/" + RESOURCE_PATTERN;
|
|
||||||
packageSearchPath = packageSearchPath.replace("//", "/"); // else it
|
|
||||||
// doesn't
|
|
||||||
// work if
|
|
||||||
// *.class are
|
|
||||||
// in WAR!!
|
|
||||||
final Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
|
|
||||||
for (final Resource resource : resources) {
|
|
||||||
if (resource.isReadable()) {
|
|
||||||
final MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
|
|
||||||
final Set<MethodMetadata> metadataSet = metadataReader.getAnnotationMetadata()
|
|
||||||
.getAnnotatedMethods(annotationClass.getName());
|
|
||||||
if (metadataSet != null && metadataSet.size() > 0) {
|
|
||||||
for (final MethodMetadata metadata : metadataSet) {
|
|
||||||
final Map<String, Object> attributes = metadata.getAnnotationAttributes(annotationClass.getName());
|
|
||||||
final JobName attributeValue = (JobName) attributes.get(attributeName);
|
|
||||||
final String className = metadata.getDeclaringClassName();
|
|
||||||
final ClassMethodNamesPair pair = new ClassMethodNamesPair();
|
|
||||||
pair.className = className;
|
|
||||||
pair.methodName = metadata.getMethodName();
|
|
||||||
targetMethosMap.put(attributeValue.toString(), pair);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-35
@@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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.annotation;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation that marks a method to be picked while scheduling a cron jobs.
|
|
||||||
*/
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface CronTarget {
|
|
||||||
|
|
||||||
JobName jobName();
|
|
||||||
}
|
|
||||||
+2
-1
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.infrastructure.jobs.data;
|
package org.apache.fineract.infrastructure.jobs.data;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
public class JobDetailData {
|
public class JobDetailData {
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ public class JobDetailData {
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final String initializingError;
|
private final String initializingError;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Getter
|
||||||
private final String cronExpression;
|
private final String cronExpression;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
|
|||||||
+40
-30
@@ -18,38 +18,43 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.jobs.service;
|
package org.apache.fineract.infrastructure.jobs.service;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public enum JobName {
|
public enum JobName {
|
||||||
|
|
||||||
UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), APPLY_ANNUAL_FEE_FOR_SAVINGS(
|
UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), //
|
||||||
"Apply Annual Fee For Savings"), APPLY_HOLIDAYS_TO_LOANS("Apply Holidays To Loans"), POST_INTEREST_FOR_SAVINGS(
|
APPLY_ANNUAL_FEE_FOR_SAVINGS("Apply Annual Fee For Savings"), //
|
||||||
"Post Interest For Savings"), TRANSFER_FEE_CHARGE_FOR_LOANS(
|
APPLY_HOLIDAYS_TO_LOANS("Apply Holidays To Loans"), //
|
||||||
"Transfer Fee For Loans From Savings"), ACCOUNTING_RUNNING_BALANCE_UPDATE(
|
POST_INTEREST_FOR_SAVINGS("Post Interest For Savings"), //
|
||||||
"Update Accounting Running Balances"), PAY_DUE_SAVINGS_CHARGES(
|
TRANSFER_FEE_CHARGE_FOR_LOANS("Transfer Fee For Loans From Savings"), //
|
||||||
"Pay Due Savings Charges"), APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT(
|
ACCOUNTING_RUNNING_BALANCE_UPDATE("Update Accounting Running Balances"), //
|
||||||
"Apply penalty to overdue loans"), EXECUTE_STANDING_INSTRUCTIONS(
|
PAY_DUE_SAVINGS_CHARGES("Pay Due Savings Charges"), //
|
||||||
"Execute Standing Instruction"), ADD_ACCRUAL_ENTRIES(
|
APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT("Apply penalty to overdue loans"), //
|
||||||
"Add Accrual Transactions"), UPDATE_NPA(
|
EXECUTE_STANDING_INSTRUCTIONS("Execute Standing Instruction"), //
|
||||||
"Update Non Performing Assets"), UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS(
|
ADD_ACCRUAL_ENTRIES("Add Accrual Transactions"), //
|
||||||
"Update Deposit Accounts Maturity details"), TRANSFER_INTEREST_TO_SAVINGS(
|
UPDATE_NPA("Update Non Performing Assets"), //
|
||||||
"Transfer Interest To Savings"), ADD_PERIODIC_ACCRUAL_ENTRIES(
|
UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS("Update Deposit Accounts Maturity details"), //
|
||||||
"Add Periodic Accrual Transactions"), RECALCULATE_INTEREST_FOR_LOAN(
|
TRANSFER_INTEREST_TO_SAVINGS("Transfer Interest To Savings"), //
|
||||||
"Recalculate Interest For Loans"), GENERATE_RD_SCEHDULE(
|
ADD_PERIODIC_ACCRUAL_ENTRIES("Add Periodic Accrual Transactions"), //
|
||||||
"Generate Mandatory Savings Schedule"), GENERATE_LOANLOSS_PROVISIONING(
|
RECALCULATE_INTEREST_FOR_LOAN("Recalculate Interest For Loans"), //
|
||||||
"Generate Loan Loss Provisioning"), POST_DIVIDENTS_FOR_SHARES(
|
GENERATE_RD_SCEHDULE("Generate Mandatory Savings Schedule"), //
|
||||||
"Post Dividends For Shares"), UPDATE_SAVINGS_DORMANT_ACCOUNTS(
|
GENERATE_LOANLOSS_PROVISIONING("Generate Loan Loss Provisioning"), //
|
||||||
"Update Savings Dormant Accounts"), ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS(
|
POST_DIVIDENTS_FOR_SHARES("Post Dividends For Shares"), //
|
||||||
"Add Accrual Transactions For Loans With Income Posted As Transactions"), EXECUTE_REPORT_MAILING_JOBS(
|
UPDATE_SAVINGS_DORMANT_ACCOUNTS("Update Savings Dormant Accounts"), //
|
||||||
"Execute Report Mailing Jobs"), UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE(
|
ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS(
|
||||||
"Update SMS Outbound with Campaign Message"), SEND_MESSAGES_TO_SMS_GATEWAY(
|
"Add Accrual Transactions For Loans With Income Posted As Transactions"), //
|
||||||
"Send Messages to SMS Gateway"), GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY(
|
EXECUTE_REPORT_MAILING_JOBS("Execute Report Mailing Jobs"), //
|
||||||
"Get Delivery Reports from SMS Gateway"), GENERATE_ADHOCCLIENT_SCEHDULE(
|
UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE("Update SMS Outbound with Campaign Message"), //
|
||||||
"Generate AdhocClient Schedule"), UPDATE_EMAIL_OUTBOUND_WITH_CAMPAIGN_MESSAGE(
|
SEND_MESSAGES_TO_SMS_GATEWAY("Send Messages to SMS Gateway"), //
|
||||||
"Update Email Outbound with campaign message"), EXECUTE_EMAIL(
|
GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY("Get Delivery Reports from SMS Gateway"), //
|
||||||
"Execute Email"), UPDATE_TRIAL_BALANCE_DETAILS(
|
GENERATE_ADHOC_CLIENT_SCHEDULE("Generate AdhocClient Schedule"), //
|
||||||
"Update Trial Balance Details"), EXECUTE_DIRTY_JOBS(
|
UPDATE_EMAIL_OUTBOUND_WITH_CAMPAIGN_MESSAGE("Update Email Outbound with campaign message"), //
|
||||||
"Execute All Dirty Jobs"), INCREASE_BUSINESS_DATE_BY_1_DAY(
|
EXECUTE_EMAIL("Execute Email"), //
|
||||||
"Increase Business Date by 1 day"), INCREASE_COB_DATE_BY_1_DAY(
|
UPDATE_TRIAL_BALANCE_DETAILS("Update Trial Balance Details"), //
|
||||||
"Increase COB Date by 1 day");
|
EXECUTE_DIRTY_JOBS("Execute All Dirty Jobs"), //
|
||||||
|
INCREASE_BUSINESS_DATE_BY_1_DAY("Increase Business Date by 1 day"), //
|
||||||
|
INCREASE_COB_DATE_BY_1_DAY("Increase COB Date by 1 day");
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
@@ -57,6 +62,11 @@ public enum JobName {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static JobName getJobByName(String jobName) {
|
||||||
|
Optional<JobName> optionalJob = Arrays.stream(JobName.values()).filter(jn -> jobName.equals(jn.name)).findAny();
|
||||||
|
return optionalJob.orElseThrow(() -> new IllegalArgumentException("Job not found by name: " + jobName));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return this.name;
|
return this.name;
|
||||||
|
|||||||
+4
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.jobs.service;
|
package org.apache.fineract.infrastructure.jobs.service;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
|
||||||
|
|
||||||
public interface JobRegisterService {
|
public interface JobRegisterService {
|
||||||
|
|
||||||
void executeJob(Long jobId);
|
void executeJob(Long jobId);
|
||||||
@@ -32,6 +34,8 @@ public interface JobRegisterService {
|
|||||||
|
|
||||||
void stopScheduler(String name);
|
void stopScheduler(String name);
|
||||||
|
|
||||||
|
void scheduleJob(ScheduledJobDetail scheduledJobDetails);
|
||||||
|
|
||||||
void stopAllSchedulers();
|
void stopAllSchedulers();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-98
@@ -20,27 +20,22 @@ package org.apache.fineract.infrastructure.jobs.service;
|
|||||||
|
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import javax.annotation.PostConstruct;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
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.JobParameter;
|
||||||
import org.apache.fineract.infrastructure.jobs.domain.JobParameterRepository;
|
import org.apache.fineract.infrastructure.jobs.domain.JobParameterRepository;
|
||||||
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
|
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
|
||||||
import org.apache.fineract.infrastructure.jobs.domain.SchedulerDetail;
|
import org.apache.fineract.infrastructure.jobs.domain.SchedulerDetail;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobNodeIdMismatchingException;
|
import org.apache.fineract.infrastructure.jobs.exception.JobNodeIdMismatchingException;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
|
import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
|
||||||
import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
|
|
||||||
import org.quartz.JobDataMap;
|
import org.quartz.JobDataMap;
|
||||||
import org.quartz.JobDetail;
|
import org.quartz.JobDetail;
|
||||||
import org.quartz.JobKey;
|
import org.quartz.JobKey;
|
||||||
@@ -49,10 +44,13 @@ import org.quartz.Scheduler;
|
|||||||
import org.quartz.SchedulerException;
|
import org.quartz.SchedulerException;
|
||||||
import org.quartz.Trigger;
|
import org.quartz.Trigger;
|
||||||
import org.quartz.TriggerListener;
|
import org.quartz.TriggerListener;
|
||||||
import org.slf4j.Logger;
|
import org.springframework.batch.core.Job;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.springframework.batch.core.JobParameters;
|
||||||
|
import org.springframework.batch.core.JobParametersBuilder;
|
||||||
|
import org.springframework.batch.core.configuration.JobLocator;
|
||||||
|
import org.springframework.batch.core.explore.JobExplorer;
|
||||||
|
import org.springframework.batch.core.launch.JobLauncher;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.context.event.ContextClosedEvent;
|
import org.springframework.context.event.ContextClosedEvent;
|
||||||
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
|
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
|
||||||
@@ -65,19 +63,12 @@ import org.springframework.stereotype.Service;
|
|||||||
* ,{@link MethodInvokingJobDetailFactoryBean} and {@link CronTriggerFactoryBean}
|
* ,{@link MethodInvokingJobDetailFactoryBean} and {@link CronTriggerFactoryBean}
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class JobRegisterServiceImpl implements JobRegisterService, ApplicationListener<ContextClosedEvent> {
|
public class JobRegisterServiceImpl implements JobRegisterService, ApplicationListener<ContextClosedEvent> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JobRegisterServiceImpl.class);
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SchedularWritePlatformService schedularWritePlatformService;
|
private SchedularWritePlatformService schedularWritePlatformService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private TenantDetailsService tenantDetailsService;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SchedulerJobListener schedulerJobListener;
|
private SchedulerJobListener schedulerJobListener;
|
||||||
|
|
||||||
@@ -87,6 +78,9 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
|||||||
@Autowired
|
@Autowired
|
||||||
private JobParameterRepository jobParameterRepository;
|
private JobParameterRepository jobParameterRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobExplorer jobExplorer;
|
||||||
|
|
||||||
private final HashMap<String, Scheduler> schedulers = new HashMap<>(4);
|
private final HashMap<String, Scheduler> schedulers = new HashMap<>(4);
|
||||||
|
|
||||||
// This cannot be injected as Autowired due to circular dependency
|
// This cannot be injected as Autowired due to circular dependency
|
||||||
@@ -95,29 +89,13 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
|||||||
@Autowired
|
@Autowired
|
||||||
private FineractProperties fineractProperties;
|
private FineractProperties fineractProperties;
|
||||||
|
|
||||||
@PostConstruct
|
@Autowired
|
||||||
public void loadAllJobs() {
|
private JobLauncher jobLauncher;
|
||||||
// If the instance is not Batch Enabled will not load the Jobs
|
|
||||||
if (!fineractProperties.getMode().isBatchManagerEnabled()) {
|
@Autowired
|
||||||
return;
|
private JobLocator jobLocator;
|
||||||
}
|
|
||||||
final List<FineractPlatformTenant> allTenants = this.tenantDetailsService.findAllTenants();
|
private static final String JOB_LAUNCHER_STARTER_METHOD_NAME = "run";
|
||||||
for (final FineractPlatformTenant tenant : allTenants) {
|
|
||||||
ThreadLocalContextUtil.setTenant(tenant);
|
|
||||||
final List<ScheduledJobDetail> scheduledJobDetails = this.schedularWritePlatformService
|
|
||||||
.retrieveAllJobs(fineractProperties.getNodeId());
|
|
||||||
for (final ScheduledJobDetail jobDetails : scheduledJobDetails) {
|
|
||||||
scheduleJob(jobDetails);
|
|
||||||
jobDetails.updateTriggerMisfired(false);
|
|
||||||
this.schedularWritePlatformService.saveOrUpdate(jobDetails);
|
|
||||||
}
|
|
||||||
final SchedulerDetail schedulerDetail = this.schedularWritePlatformService.retriveSchedulerDetail();
|
|
||||||
if (schedulerDetail.isResetSchedulerOnBootup()) {
|
|
||||||
schedulerDetail.updateSuspendedState(false);
|
|
||||||
this.schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void executeJob(final ScheduledJobDetail scheduledJobDetail, String triggerType) {
|
public void executeJob(final ScheduledJobDetail scheduledJobDetail, String triggerType) {
|
||||||
try {
|
try {
|
||||||
@@ -127,25 +105,24 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
|||||||
}
|
}
|
||||||
jobDataMap.put(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE, triggerType);
|
jobDataMap.put(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE, triggerType);
|
||||||
jobDataMap.put(SchedulerServiceConstants.TENANT_IDENTIFIER, ThreadLocalContextUtil.getTenant().getTenantIdentifier());
|
jobDataMap.put(SchedulerServiceConstants.TENANT_IDENTIFIER, ThreadLocalContextUtil.getTenant().getTenantIdentifier());
|
||||||
final String key = scheduledJobDetail.getJobKey();
|
|
||||||
final JobKey jobKey = constructJobKey(key);
|
|
||||||
final String schedulerName = getSchedulerName(scheduledJobDetail);
|
final String schedulerName = getSchedulerName(scheduledJobDetail);
|
||||||
final Scheduler scheduler = this.schedulers.get(schedulerName);
|
final Scheduler scheduler = this.schedulers.get(schedulerName);
|
||||||
|
final JobDetail jobDetail = createJobDetail(scheduledJobDetail);
|
||||||
|
JobKey jobKey = jobDetail.getKey();
|
||||||
if (scheduler == null || !scheduler.checkExists(jobKey)) {
|
if (scheduler == null || !scheduler.checkExists(jobKey)) {
|
||||||
final JobDetail jobDetail = createJobDetail(scheduledJobDetail);
|
|
||||||
final String tempSchedulerName = "temp" + scheduledJobDetail.getId();
|
final String tempSchedulerName = "temp" + scheduledJobDetail.getId();
|
||||||
final Scheduler tempScheduler = createScheduler(tempSchedulerName, 1, schedulerJobListener, schedulerStopListener);
|
final Scheduler tempScheduler = createScheduler(tempSchedulerName, 1, schedulerJobListener, schedulerStopListener);
|
||||||
tempScheduler.addJob(jobDetail, true);
|
|
||||||
jobDataMap.put(SchedulerServiceConstants.SCHEDULER_NAME, tempSchedulerName);
|
jobDataMap.put(SchedulerServiceConstants.SCHEDULER_NAME, tempSchedulerName);
|
||||||
this.schedulers.put(tempSchedulerName, tempScheduler);
|
schedulers.put(tempSchedulerName, tempScheduler);
|
||||||
tempScheduler.triggerJob(jobDetail.getKey(), jobDataMap);
|
tempScheduler.addJob(jobDetail, true);
|
||||||
|
tempScheduler.triggerJob(jobKey, jobDataMap);
|
||||||
} else {
|
} else {
|
||||||
|
scheduler.addJob(jobDetail, true);
|
||||||
scheduler.triggerJob(jobKey, jobDataMap);
|
scheduler.triggerJob(jobKey, jobDataMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
final String msg = "Job execution failed for job with id:" + scheduledJobDetail.getId();
|
final String msg = "Job execution failed for job with id:" + scheduledJobDetail.getId();
|
||||||
LOG.error("{}", msg, e);
|
log.error("{}", msg, e);
|
||||||
throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", msg, scheduledJobDetail.getId(), e);
|
throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", msg, scheduledJobDetail.getId(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +183,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final SchedulerException e) {
|
} catch (final SchedulerException e) {
|
||||||
LOG.error("Error occured.", e);
|
log.error("Error occured.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jobDetail.updateTriggerMisfired(false);
|
jobDetail.updateTriggerMisfired(false);
|
||||||
@@ -264,7 +241,8 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
|||||||
this.stopAllSchedulers();
|
this.stopAllSchedulers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleJob(final ScheduledJobDetail scheduledJobDetails) {
|
@Override
|
||||||
|
public void scheduleJob(final ScheduledJobDetail scheduledJobDetails) {
|
||||||
if (!scheduledJobDetails.isActiveSchedular()) {
|
if (!scheduledJobDetails.isActiveSchedular()) {
|
||||||
scheduledJobDetails.updateNextRunTime(null);
|
scheduledJobDetails.updateNextRunTime(null);
|
||||||
scheduledJobDetails.updateCurrentlyRunningStatus(false);
|
scheduledJobDetails.updateCurrentlyRunningStatus(false);
|
||||||
@@ -282,7 +260,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
|||||||
scheduledJobDetails.updateNextRunTime(null);
|
scheduledJobDetails.updateNextRunTime(null);
|
||||||
final String stackTrace = getStackTraceAsString(throwable);
|
final String stackTrace = getStackTraceAsString(throwable);
|
||||||
scheduledJobDetails.updateErrorLog(stackTrace);
|
scheduledJobDetails.updateErrorLog(stackTrace);
|
||||||
LOG.error("Could not schedule job: {}", scheduledJobDetails.getJobName(), throwable);
|
log.error("Could not schedule job: {}", scheduledJobDetails.getJobName(), throwable);
|
||||||
}
|
}
|
||||||
scheduledJobDetails.updateCurrentlyRunningStatus(false);
|
scheduledJobDetails.updateCurrentlyRunningStatus(false);
|
||||||
}
|
}
|
||||||
@@ -293,7 +271,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
|||||||
try {
|
try {
|
||||||
scheduler.shutdown();
|
scheduler.shutdown();
|
||||||
} catch (final SchedulerException e) {
|
} catch (final SchedulerException e) {
|
||||||
LOG.error("Error occured.", e);
|
log.error("Error occured.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,7 +296,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
|||||||
try {
|
try {
|
||||||
scheduler.shutdown();
|
scheduler.shutdown();
|
||||||
} catch (final SchedulerException e) {
|
} catch (final SchedulerException e) {
|
||||||
LOG.error("Error occured.", e);
|
log.error("Error occurred.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,65 +326,36 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
|||||||
|
|
||||||
private JobDetail createJobDetail(final ScheduledJobDetail scheduledJobDetail) throws Exception {
|
private JobDetail createJobDetail(final ScheduledJobDetail scheduledJobDetail) throws Exception {
|
||||||
final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
|
final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
|
||||||
final ClassMethodNamesPair jobDetails = CronMethodParser.findTargetMethodDetails(scheduledJobDetail.getJobName());
|
|
||||||
if (jobDetails == null) {
|
JobName jobName = JobName.getJobByName(scheduledJobDetail.getJobName());
|
||||||
throw new IllegalArgumentException(
|
Job job = jobLocator.getJob(jobName.name());
|
||||||
"Code has no @CronTarget with this job name (@see JobName); seems like DB/code are not in line: "
|
|
||||||
+ scheduledJobDetail.getJobName());
|
|
||||||
}
|
|
||||||
final Object targetObject = getBeanObject(Class.forName(jobDetails.className));
|
|
||||||
final MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean();
|
final MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean();
|
||||||
jobDetailFactoryBean.setName(scheduledJobDetail.getJobName() + "JobDetail" + tenant.getId());
|
jobDetailFactoryBean.setName(scheduledJobDetail.getJobName() + "JobDetail" + tenant.getId());
|
||||||
jobDetailFactoryBean.setTargetObject(targetObject);
|
jobDetailFactoryBean.setTargetObject(jobLauncher);
|
||||||
jobDetailFactoryBean.setTargetMethod(jobDetails.methodName);
|
jobDetailFactoryBean.setTargetMethod(JOB_LAUNCHER_STARTER_METHOD_NAME);
|
||||||
jobDetailFactoryBean.setGroup(scheduledJobDetail.getGroupName());
|
jobDetailFactoryBean.setGroup(scheduledJobDetail.getGroupName());
|
||||||
jobDetailFactoryBean.setConcurrent(false);
|
jobDetailFactoryBean.setConcurrent(false);
|
||||||
Map<String, String> jobParameterMap = getJobParameter(scheduledJobDetail);
|
|
||||||
if (!jobParameterMap.isEmpty()) {
|
Map<String, org.springframework.batch.core.JobParameter> jobParameterMap = getJobParameter(scheduledJobDetail);
|
||||||
jobDetailFactoryBean.setArguments(jobParameterMap);
|
JobParameters jobParameters = new JobParametersBuilder(jobExplorer).getNextJobParameters(job)
|
||||||
}
|
.addJobParameters(new JobParameters(jobParameterMap)).toJobParameters();
|
||||||
|
|
||||||
|
jobDetailFactoryBean.setArguments(job, jobParameters);
|
||||||
jobDetailFactoryBean.afterPropertiesSet();
|
jobDetailFactoryBean.afterPropertiesSet();
|
||||||
return jobDetailFactoryBean.getObject();
|
return jobDetailFactoryBean.getObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getJobParameter(ScheduledJobDetail scheduledJobDetail) {
|
public Map<String, org.springframework.batch.core.JobParameter> getJobParameter(ScheduledJobDetail scheduledJobDetail) {
|
||||||
List<JobParameter> jobParameterList = jobParameterRepository.findJobParametersByJobId(scheduledJobDetail.getId());
|
List<JobParameter> jobParameterList = jobParameterRepository.findJobParametersByJobId(scheduledJobDetail.getId());
|
||||||
Map<String, String> jobParameterMap = new HashMap<>();
|
Map<String, org.springframework.batch.core.JobParameter> jobParameterMap = new HashMap<>();
|
||||||
for (JobParameter jobparameter : jobParameterList) {
|
for (JobParameter jobparameter : jobParameterList) {
|
||||||
jobParameterMap.put(jobparameter.getParameterName(), jobparameter.getParameterValue());
|
jobParameterMap.put(jobparameter.getParameterName(),
|
||||||
|
new org.springframework.batch.core.JobParameter(jobparameter.getParameterValue()));
|
||||||
}
|
}
|
||||||
return jobParameterMap;
|
return jobParameterMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object getBeanObject(final Class<?> classType) throws ClassNotFoundException {
|
|
||||||
final List<Class<?>> typesList = new ArrayList<>();
|
|
||||||
final Class<?>[] interfaceType = classType.getInterfaces();
|
|
||||||
if (interfaceType.length > 0) {
|
|
||||||
typesList.addAll(Arrays.asList(interfaceType));
|
|
||||||
} else {
|
|
||||||
Class<?> superclassType = classType;
|
|
||||||
while (!Object.class.getName().equals(superclassType.getSuperclass().getName())) {
|
|
||||||
superclassType = superclassType.getSuperclass();
|
|
||||||
}
|
|
||||||
typesList.add(superclassType);
|
|
||||||
}
|
|
||||||
final List<String> beanNames = new ArrayList<>();
|
|
||||||
for (final Class<?> clazz : typesList) {
|
|
||||||
beanNames.addAll(Arrays.asList(this.applicationContext.getBeanNamesForType(clazz)));
|
|
||||||
}
|
|
||||||
Object targetObject = null;
|
|
||||||
for (final String beanName : beanNames) {
|
|
||||||
final Object nextObject = this.applicationContext.getBean(beanName);
|
|
||||||
String targetObjName = nextObject.toString();
|
|
||||||
targetObjName = targetObjName.substring(0, targetObjName.lastIndexOf("@"));
|
|
||||||
if (classType.getName().equals(targetObjName)) {
|
|
||||||
targetObject = nextObject;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return targetObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Trigger createTrigger(final ScheduledJobDetail scheduledJobDetails, final JobDetail jobDetail) throws ParseException {
|
private Trigger createTrigger(final ScheduledJobDetail scheduledJobDetails, final JobDetail jobDetail) throws ParseException {
|
||||||
final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
|
final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
|
||||||
final CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
|
final CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
|
||||||
|
|||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* 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.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.domain.SchedulerDetail;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.event.ContextRefreshedEvent;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class JobSchedulerServiceImpl implements ApplicationListener<ContextRefreshedEvent> {
|
||||||
|
|
||||||
|
private final FineractProperties fineractProperties;
|
||||||
|
private final SchedularWritePlatformService schedularWritePlatformService;
|
||||||
|
private final TenantDetailsService tenantDetailsService;
|
||||||
|
private final JobRegisterService jobRegisterService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||||
|
// If the instance is not Batch Enabled will not load the Jobs
|
||||||
|
if (!fineractProperties.getMode().isBatchManagerEnabled()) {
|
||||||
|
log.warn("Batch job scheduling is disabled since this instance is not a batch manager");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final List<FineractPlatformTenant> allTenants = tenantDetailsService.findAllTenants();
|
||||||
|
for (final FineractPlatformTenant tenant : allTenants) {
|
||||||
|
ThreadLocalContextUtil.setTenant(tenant);
|
||||||
|
final List<ScheduledJobDetail> scheduledJobDetails = schedularWritePlatformService
|
||||||
|
.retrieveAllJobs(fineractProperties.getNodeId());
|
||||||
|
for (final ScheduledJobDetail jobDetails : scheduledJobDetails) {
|
||||||
|
jobRegisterService.scheduleJob(jobDetails);
|
||||||
|
jobDetails.updateTriggerMisfired(false);
|
||||||
|
schedularWritePlatformService.saveOrUpdate(jobDetails);
|
||||||
|
}
|
||||||
|
final SchedulerDetail schedulerDetail = schedularWritePlatformService.retriveSchedulerDetail();
|
||||||
|
if (schedulerDetail.isResetSchedulerOnBootup()) {
|
||||||
|
schedulerDetail.updateSuspendedState(false);
|
||||||
|
schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("Scheduling batch jobs has finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -30,6 +30,8 @@ public interface SchedulerJobRunnerReadService {
|
|||||||
|
|
||||||
JobDetailData retrieveOne(Long jobId);
|
JobDetailData retrieveOne(Long jobId);
|
||||||
|
|
||||||
|
JobDetailData retrieveOneByName(String jobName);
|
||||||
|
|
||||||
Page<JobDetailHistoryData> retrieveJobHistory(Long jobId, SearchParameters searchParameters);
|
Page<JobDetailHistoryData> retrieveJobHistory(Long jobId, SearchParameters searchParameters);
|
||||||
|
|
||||||
boolean isUpdatesAllowed();
|
boolean isUpdatesAllowed();
|
||||||
|
|||||||
+14
-3
@@ -77,6 +77,17 @@ public class SchedulerJobRunnerReadServiceImpl implements SchedulerJobRunnerRead
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JobDetailData retrieveOneByName(String jobName) {
|
||||||
|
try {
|
||||||
|
final JobDetailMapper detailMapper = new JobDetailMapper(sqlGenerator);
|
||||||
|
final String sql = detailMapper.schema() + " where job.name=?";
|
||||||
|
return this.jdbcTemplate.queryForObject(sql, detailMapper, new Object[] { jobName }); // NOSONAR
|
||||||
|
} catch (final EmptyResultDataAccessException e) {
|
||||||
|
throw new JobNotFoundException(jobName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<JobDetailHistoryData> retrieveJobHistory(final Long jobId, final SearchParameters searchParameters) {
|
public Page<JobDetailHistoryData> retrieveJobHistory(final Long jobId, final SearchParameters searchParameters) {
|
||||||
if (!isJobExist(jobId)) {
|
if (!isJobExist(jobId)) {
|
||||||
@@ -142,9 +153,9 @@ public class SchedulerJobRunnerReadServiceImpl implements SchedulerJobRunnerRead
|
|||||||
JobDetailMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
|
JobDetailMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||||
sqlBuilder = new StringBuilder("select").append(
|
sqlBuilder = new StringBuilder("select").append(
|
||||||
" job.id,job.display_name as displayName,job.next_run_time as nextRunTime,job.initializing_errorlog as initializingError,job.cron_expression as cronExpression,job.is_active as active,job.currently_running as currentlyRunning,")
|
" job.id,job.display_name as displayName,job.next_run_time as nextRunTime,job.initializing_errorlog as initializingError,job.cron_expression as cronExpression,job.is_active as active,job.currently_running as currentlyRunning,")
|
||||||
.append(" runHistory.version,runHistory.start_time as lastRunStartTime,runHistory.end_time as lastRunEndTime,runHistory."
|
.append(" runHistory.version,runHistory.start_time as lastRunStartTime,runHistory.end_time as lastRunEndTime,runHistory.")
|
||||||
+ sqlGenerator.escape("status")
|
.append(sqlGenerator.escape("status"))
|
||||||
+ ",runHistory.error_message as jobRunErrorMessage,runHistory.trigger_type as triggerType,runHistory.error_log as jobRunErrorLog ")
|
.append(",runHistory.error_message as jobRunErrorMessage,runHistory.trigger_type as triggerType,runHistory.error_log as jobRunErrorLog ")
|
||||||
.append(" from job job left join job_run_history runHistory ON job.id=runHistory.job_id and job.previous_run_start_time=runHistory.start_time ");
|
.append(" from job job left join job_run_history runHistory ON job.id=runHistory.job_id and job.previous_run_start_time=runHistory.start_time ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
@@ -36,6 +36,8 @@ import org.quartz.TriggerListener;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Isolation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -62,6 +64,7 @@ public class SchedulerTriggerListener implements TriggerListener {
|
|||||||
@Override
|
@Override
|
||||||
@SuppressFBWarnings(value = {
|
@SuppressFBWarnings(value = {
|
||||||
"DMI_RANDOM_USED_ONLY_ONCE" }, justification = "False positive for random object created and used only once")
|
"DMI_RANDOM_USED_ONLY_ONCE" }, justification = "False positive for random object created and used only once")
|
||||||
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||||
public boolean vetoJobExecution(final Trigger trigger, final JobExecutionContext context) {
|
public boolean vetoJobExecution(final Trigger trigger, final JobExecutionContext context) {
|
||||||
final String tenantIdentifier = trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
|
final String tenantIdentifier = trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
|
||||||
final FineractPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantIdentifier);
|
final FineractPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantIdentifier);
|
||||||
|
|||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 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.service.executealldirtyjobs;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ExecuteAllDirtyJobsConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step executeAllDirtyJobsStep(ExecuteAllDirtyJobsTasklet executeAllDirtyJobsTasklet) {
|
||||||
|
return steps.get(JobName.EXECUTE_DIRTY_JOBS.name()).tasklet(executeAllDirtyJobsTasklet).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job executeAllDirtyJobsJob(ExecuteAllDirtyJobsTasklet executeAllDirtyJobsTasklet) {
|
||||||
|
return jobs.get(JobName.EXECUTE_DIRTY_JOBS.name()).start(executeAllDirtyJobsStep(executeAllDirtyJobsTasklet))
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* 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.service.executealldirtyjobs;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobRegisterService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
public class ExecuteAllDirtyJobsTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final JobRegisterService jobRegisterService;
|
||||||
|
private final ScheduledJobDetailRepository scheduledJobDetailsRepository;
|
||||||
|
private final FineractProperties fineractProperties;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
List<ScheduledJobDetail> jobDetails = scheduledJobDetailsRepository.findAllMismatchedJobs(true);
|
||||||
|
|
||||||
|
for (ScheduledJobDetail scheduledJobDetail : jobDetails) {
|
||||||
|
if (scheduledJobDetail.getNodeId().toString().equals(fineractProperties.getNodeId())) {
|
||||||
|
jobRegisterService.executeJob(scheduledJobDetail.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-8
@@ -16,15 +16,11 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.savings.service;
|
package org.apache.fineract.infrastructure.jobs.service.increasedateby1day;
|
||||||
|
|
||||||
import java.util.Map;
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
|
||||||
|
|
||||||
public interface SavingsSchedularService {
|
public interface IncreaseDateBy1DayService {
|
||||||
|
|
||||||
void postInterestForAccounts(Map<String, String> jobParameters) throws JobExecutionException;
|
|
||||||
|
|
||||||
void updateSavingsDormancyStatus() throws JobExecutionException;
|
|
||||||
|
|
||||||
|
void increaseDateByTypeByOneDay(BusinessDateType businessDateType);
|
||||||
}
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* 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.service.increasedateby1day;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateData;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDate;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateRepository;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateWritePlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class IncreaseDateBy1DayServiceImpl implements IncreaseDateBy1DayService {
|
||||||
|
|
||||||
|
private final BusinessDateRepository businessDateRepository;
|
||||||
|
private final BusinessDateWritePlatformService businessDateWritePlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void increaseDateByTypeByOneDay(BusinessDateType businessDateType) {
|
||||||
|
Map<String, Object> changes = new HashMap<>();
|
||||||
|
Optional<BusinessDate> businessDateEntity = businessDateRepository.findByType(businessDateType);
|
||||||
|
LocalDate businessDate = businessDateEntity.map(BusinessDate::getDate).orElse(DateUtils.getLocalDateOfTenant());
|
||||||
|
businessDate = businessDate.plusDays(1);
|
||||||
|
try {
|
||||||
|
BusinessDateData businessDateData = BusinessDateData.instance(businessDateType, businessDate);
|
||||||
|
businessDateWritePlatformService.adjustDate(businessDateData, changes);
|
||||||
|
} catch (final PlatformApiDataValidationException e) {
|
||||||
|
final List<ApiParameterError> errors = e.getErrors();
|
||||||
|
for (final ApiParameterError error : errors) {
|
||||||
|
log.error("Increasing {} by 1 day failed due to: {}", businessDateType.getDescription(), error.getDeveloperMessage());
|
||||||
|
}
|
||||||
|
} catch (final AbstractPlatformDomainRuleException e) {
|
||||||
|
log.error("Increasing {} by 1 day failed due to: {}", businessDateType.getDescription(), e.getDefaultUserMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Increasing {} by 1 day failed due to: {}", businessDateType.getDescription(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* 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.service.increasedateby1day.increasebusinessdateby1day;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.increasedateby1day.IncreaseDateBy1DayService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class IncreaseBusinessDateBy1DayConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private IncreaseDateBy1DayService increaseDateBy1DayService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step increaseBusinessDateBy1DayStep() {
|
||||||
|
return steps.get(JobName.INCREASE_BUSINESS_DATE_BY_1_DAY.name()).tasklet(increaseBusinessDateBy1DayTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job increaseBusinessDateBy1DayJob() {
|
||||||
|
return jobs.get(JobName.INCREASE_BUSINESS_DATE_BY_1_DAY.name()).start(increaseBusinessDateBy1DayStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IncreaseBusinessDateBy1DayTasklet increaseBusinessDateBy1DayTasklet() {
|
||||||
|
return new IncreaseBusinessDateBy1DayTasklet(increaseDateBy1DayService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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.service.increasedateby1day.increasebusinessdateby1day;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.increasedateby1day.IncreaseDateBy1DayService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class IncreaseBusinessDateBy1DayTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final IncreaseDateBy1DayService increaseDateBy1DayService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
increaseDateBy1DayService.increaseDateByTypeByOneDay(BusinessDateType.BUSINESS_DATE);
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* 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.service.increasedateby1day.increasecobdateby1day;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.increasedateby1day.IncreaseDateBy1DayService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class IncreaseCobDateBy1DayConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private IncreaseDateBy1DayService increaseDateBy1DayService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step increaseCobDateBy1DayStep() {
|
||||||
|
return steps.get(JobName.INCREASE_COB_DATE_BY_1_DAY.name()).tasklet(increaseCobDateBy1DayTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job increaseCobDateBy1DayJob() {
|
||||||
|
return jobs.get(JobName.INCREASE_COB_DATE_BY_1_DAY.name()).start(increaseCobDateBy1DayStep()).incrementer(new RunIdIncrementer())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IncreaseCobDateBy1DayTasklet increaseCobDateBy1DayTasklet() {
|
||||||
|
return new IncreaseCobDateBy1DayTasklet(increaseDateBy1DayService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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.service.increasedateby1day.increasecobdateby1day;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.increasedateby1day.IncreaseDateBy1DayService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class IncreaseCobDateBy1DayTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final IncreaseDateBy1DayService increaseDateBy1DayService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
increaseDateBy1DayService.increaseDateByTypeByOneDay(BusinessDateType.COB_DATE);
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* 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.service.updatenpa;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.service.RoutingDataSourceServiceFactory;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class UpdateNpaConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private RoutingDataSourceServiceFactory dataSourceServiceFactory;
|
||||||
|
@Autowired
|
||||||
|
private DatabaseTypeResolver databaseTypeResolver;
|
||||||
|
@Autowired
|
||||||
|
private DatabaseSpecificSQLGenerator sqlGenerator;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step updateNpaStep() {
|
||||||
|
return steps.get(JobName.UPDATE_NPA.name()).tasklet(updateNpaTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job updateNpaJob() {
|
||||||
|
return jobs.get(JobName.UPDATE_NPA.name()).start(updateNpaStep()).incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UpdateNpaTasklet updateNpaTasklet() {
|
||||||
|
return new UpdateNpaTasklet(dataSourceServiceFactory, databaseTypeResolver, sqlGenerator);
|
||||||
|
}
|
||||||
|
}
|
||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* 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.service.updatenpa;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.RoutingDataSourceServiceFactory;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpdateNpaTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final RoutingDataSourceServiceFactory dataSourceServiceFactory;
|
||||||
|
private final DatabaseTypeResolver databaseTypeResolver;
|
||||||
|
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
|
||||||
|
|
||||||
|
final StringBuilder resetNPASqlBuilder = new StringBuilder();
|
||||||
|
resetNPASqlBuilder.append("update m_loan loan ");
|
||||||
|
String fromPart = " (SELECT loan2.* FROM m_loan loan2 left join m_loan_arrears_aging laa on laa.loan_id = loan2.id "
|
||||||
|
+ "inner join m_product_loan mpl on mpl.id = loan2.product_id and mpl.overdue_days_for_npa is not null "
|
||||||
|
+ "WHERE loan2.loan_status_id = 300 and mpl.account_moves_out_of_npa_only_on_arrears_completion = false"
|
||||||
|
+ " or (mpl.account_moves_out_of_npa_only_on_arrears_completion = true"
|
||||||
|
+ " and laa.overdue_since_date_derived is null)) sl";
|
||||||
|
String wherePart = " where loan.id = sl.id ";
|
||||||
|
|
||||||
|
if (databaseTypeResolver.isMySQL()) {
|
||||||
|
resetNPASqlBuilder.append(", ").append(fromPart).append(" set loan.is_npa = false").append(wherePart);
|
||||||
|
} else {
|
||||||
|
resetNPASqlBuilder.append("set is_npa = false").append(" FROM ").append(fromPart).append(wherePart);
|
||||||
|
}
|
||||||
|
jdbcTemplate.update(resetNPASqlBuilder.toString());
|
||||||
|
|
||||||
|
final StringBuilder updateSqlBuilder = new StringBuilder(900);
|
||||||
|
|
||||||
|
fromPart = " (select loan.id " + " FROM m_loan_arrears_aging laa" + " INNER JOIN m_loan loan on laa.loan_id = loan.id "
|
||||||
|
+ " INNER JOIN m_product_loan mpl on mpl.id = loan.product_id AND mpl.overdue_days_for_npa is not null "
|
||||||
|
+ "WHERE loan.loan_status_id = 300 and " + "laa.overdue_since_date_derived < "
|
||||||
|
+ sqlGenerator.subDate(sqlGenerator.currentBusinessDate(), "COALESCE(mpl.overdue_days_for_npa, 0)", "day")
|
||||||
|
+ " group by loan.id) as sl ";
|
||||||
|
wherePart = " where ml.id=sl.id ";
|
||||||
|
updateSqlBuilder.append("UPDATE m_loan as ml ");
|
||||||
|
if (databaseTypeResolver.isMySQL()) {
|
||||||
|
updateSqlBuilder.append(", ").append(fromPart).append(" SET ml.is_npa = true").append(wherePart);
|
||||||
|
} else {
|
||||||
|
updateSqlBuilder.append(" SET is_npa = true").append(" FROM ").append(fromPart).append(wherePart);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int result = jdbcTemplate.update(updateSqlBuilder.toString());
|
||||||
|
|
||||||
|
log.info("{}: Records affected by updateNPA: {}", ThreadLocalContextUtil.getTenant().getName(), result);
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
-3
@@ -20,7 +20,6 @@ package org.apache.fineract.infrastructure.reportmailingjob.service;
|
|||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
|
||||||
|
|
||||||
public interface ReportMailingJobWritePlatformService {
|
public interface ReportMailingJobWritePlatformService {
|
||||||
|
|
||||||
@@ -29,6 +28,4 @@ public interface ReportMailingJobWritePlatformService {
|
|||||||
CommandProcessingResult updateReportMailingJob(Long reportMailingJobId, JsonCommand jsonCommand);
|
CommandProcessingResult updateReportMailingJob(Long reportMailingJobId, JsonCommand jsonCommand);
|
||||||
|
|
||||||
CommandProcessingResult deleteReportMailingJob(Long reportMailingJobId);
|
CommandProcessingResult deleteReportMailingJob(Long reportMailingJobId);
|
||||||
|
|
||||||
void executeReportMailingJobs() throws JobExecutionException;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-256
@@ -18,18 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.reportmailingjob.service;
|
package org.apache.fineract.infrastructure.reportmailingjob.service;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
@@ -38,29 +29,14 @@ import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityEx
|
|||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.domain.Report;
|
import org.apache.fineract.infrastructure.dataqueries.domain.Report;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.domain.ReportRepositoryWrapper;
|
import org.apache.fineract.infrastructure.dataqueries.domain.ReportRepositoryWrapper;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
|
|
||||||
import org.apache.fineract.infrastructure.documentmanagement.contentrepository.FileSystemContentRepository;
|
|
||||||
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.infrastructure.report.provider.ReportingProcessServiceProvider;
|
|
||||||
import org.apache.fineract.infrastructure.report.service.ReportingProcessService;
|
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConstants;
|
import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConstants;
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailAttachmentFileFormat;
|
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailData;
|
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobPreviousRunStatus;
|
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobStretchyReportParamDateOption;
|
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJob;
|
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJob;
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRepository;
|
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRepository;
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRepositoryWrapper;
|
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRepositoryWrapper;
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRunHistory;
|
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRunHistoryRepository;
|
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.util.ReportMailingJobDateUtil;
|
|
||||||
import org.apache.fineract.infrastructure.reportmailingjob.validation.ReportMailingJobValidator;
|
import org.apache.fineract.infrastructure.reportmailingjob.validation.ReportMailingJobValidator;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -79,29 +55,17 @@ public class ReportMailingJobWritePlatformServiceImpl implements ReportMailingJo
|
|||||||
private final ReportMailingJobRepositoryWrapper reportMailingJobRepositoryWrapper;
|
private final ReportMailingJobRepositoryWrapper reportMailingJobRepositoryWrapper;
|
||||||
private final ReportMailingJobRepository reportMailingJobRepository;
|
private final ReportMailingJobRepository reportMailingJobRepository;
|
||||||
private final PlatformSecurityContext platformSecurityContext;
|
private final PlatformSecurityContext platformSecurityContext;
|
||||||
private final ReportMailingJobEmailService reportMailingJobEmailService;
|
|
||||||
private final ReadReportingService readReportingService;
|
|
||||||
private final ReportingProcessServiceProvider reportingProcessServiceProvider;
|
|
||||||
private final ReportMailingJobRunHistoryRepository reportMailingJobRunHistoryRepository;
|
|
||||||
private static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public ReportMailingJobWritePlatformServiceImpl(final ReportRepositoryWrapper reportRepositoryWrapper,
|
public ReportMailingJobWritePlatformServiceImpl(final ReportRepositoryWrapper reportRepositoryWrapper,
|
||||||
final ReportMailingJobValidator reportMailingJobValidator,
|
final ReportMailingJobValidator reportMailingJobValidator,
|
||||||
final ReportMailingJobRepositoryWrapper reportMailingJobRepositoryWrapper,
|
final ReportMailingJobRepositoryWrapper reportMailingJobRepositoryWrapper,
|
||||||
final ReportMailingJobRepository reportMailingJobRepository, final PlatformSecurityContext platformSecurityContext,
|
final PlatformSecurityContext platformSecurityContext) {
|
||||||
final ReportMailingJobEmailService reportMailingJobEmailService, final ReadReportingService readReportingService,
|
|
||||||
final ReportMailingJobRunHistoryRepository reportMailingJobRunHistoryRepository,
|
|
||||||
final ReportingProcessServiceProvider reportingProcessServiceProvider) {
|
|
||||||
this.reportRepositoryWrapper = reportRepositoryWrapper;
|
this.reportRepositoryWrapper = reportRepositoryWrapper;
|
||||||
this.reportMailingJobValidator = reportMailingJobValidator;
|
this.reportMailingJobValidator = reportMailingJobValidator;
|
||||||
this.reportMailingJobRepositoryWrapper = reportMailingJobRepositoryWrapper;
|
this.reportMailingJobRepositoryWrapper = reportMailingJobRepositoryWrapper;
|
||||||
this.reportMailingJobRepository = reportMailingJobRepositoryWrapper.getReportMailingJobRepository();
|
this.reportMailingJobRepository = reportMailingJobRepositoryWrapper.getReportMailingJobRepository();
|
||||||
this.platformSecurityContext = platformSecurityContext;
|
this.platformSecurityContext = platformSecurityContext;
|
||||||
this.reportMailingJobEmailService = reportMailingJobEmailService;
|
|
||||||
this.readReportingService = readReportingService;
|
|
||||||
this.reportMailingJobRunHistoryRepository = reportMailingJobRunHistoryRepository;
|
|
||||||
this.reportingProcessServiceProvider = reportingProcessServiceProvider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -242,114 +206,6 @@ public class ReportMailingJobWritePlatformServiceImpl implements ReportMailingJo
|
|||||||
return new CommandProcessingResultBuilder().withEntityId(reportMailingJobId).build();
|
return new CommandProcessingResultBuilder().withEntityId(reportMailingJobId).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.EXECUTE_REPORT_MAILING_JOBS)
|
|
||||||
public void executeReportMailingJobs() throws JobExecutionException {
|
|
||||||
final Collection<ReportMailingJob> reportMailingJobCollection = this.reportMailingJobRepository
|
|
||||||
.findByIsActiveTrueAndIsDeletedFalse();
|
|
||||||
|
|
||||||
for (ReportMailingJob reportMailingJob : reportMailingJobCollection) {
|
|
||||||
final LocalDateTime localDateTimeOftenant = DateUtils.getLocalDateTimeOfTenant();
|
|
||||||
final LocalDateTime nextRunDateTime = reportMailingJob.getNextRunDateTime();
|
|
||||||
|
|
||||||
if (nextRunDateTime != null && nextRunDateTime.isBefore(localDateTimeOftenant)) {
|
|
||||||
// get the emailAttachmentFileFormat enum object
|
|
||||||
final ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat = ReportMailingJobEmailAttachmentFileFormat
|
|
||||||
.newInstance(reportMailingJob.getEmailAttachmentFileFormat());
|
|
||||||
|
|
||||||
if (emailAttachmentFileFormat != null && emailAttachmentFileFormat.isValid()) {
|
|
||||||
final Report stretchyReport = reportMailingJob.getStretchyReport();
|
|
||||||
final String reportName = (stretchyReport != null) ? stretchyReport.getReportName() : null;
|
|
||||||
final StringBuilder errorLog = new StringBuilder();
|
|
||||||
final Map<String, String> validateStretchyReportParamMap = this.reportMailingJobValidator
|
|
||||||
.validateStretchyReportParamMap(reportMailingJob.getStretchyReportParamMap());
|
|
||||||
MultivaluedMap<String, String> reportParams = new MultivaluedStringMap();
|
|
||||||
|
|
||||||
if (validateStretchyReportParamMap != null) {
|
|
||||||
Iterator<Map.Entry<String, String>> validateStretchyReportParamMapEntries = validateStretchyReportParamMap
|
|
||||||
.entrySet().iterator();
|
|
||||||
|
|
||||||
while (validateStretchyReportParamMapEntries.hasNext()) {
|
|
||||||
Map.Entry<String, String> validateStretchyReportParamMapEntry = validateStretchyReportParamMapEntries.next();
|
|
||||||
String key = validateStretchyReportParamMapEntry.getKey();
|
|
||||||
String value = validateStretchyReportParamMapEntry.getValue();
|
|
||||||
|
|
||||||
if (StringUtils.containsIgnoreCase(key, "date")) {
|
|
||||||
ReportMailingJobStretchyReportParamDateOption reportMailingJobStretchyReportParamDateOption = ReportMailingJobStretchyReportParamDateOption
|
|
||||||
.newInstance(value);
|
|
||||||
|
|
||||||
if (reportMailingJobStretchyReportParamDateOption.isValid()) {
|
|
||||||
value = ReportMailingJobDateUtil.getDateAsString(reportMailingJobStretchyReportParamDateOption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reportParams.add(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate the report output stream, method in turn call
|
|
||||||
// another that sends the file to the email recipients
|
|
||||||
this.generateReportOutputStream(reportMailingJob, emailAttachmentFileFormat, reportParams, reportName, errorLog);
|
|
||||||
|
|
||||||
// update the previous run time, next run time, status,
|
|
||||||
// error log properties
|
|
||||||
this.updateReportMailingJobAfterJobExecution(reportMailingJob, errorLog, localDateTimeOftenant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* update the report mailing job entity after job execution
|
|
||||||
*
|
|
||||||
* @param reportMailingJob
|
|
||||||
* -- the report mailing job entity
|
|
||||||
* @param errorLog
|
|
||||||
* -- StringBuilder object containing the error log if any
|
|
||||||
* @param jobStartDateTime
|
|
||||||
* -- the start LocalDateTime of the job
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
private void updateReportMailingJobAfterJobExecution(final ReportMailingJob reportMailingJob, final StringBuilder errorLog,
|
|
||||||
final LocalDateTime jobStartDateTime) {
|
|
||||||
final String recurrence = reportMailingJob.getRecurrence();
|
|
||||||
final LocalDateTime nextRunDateTime = reportMailingJob.getNextRunDateTime();
|
|
||||||
ReportMailingJobPreviousRunStatus reportMailingJobPreviousRunStatus = ReportMailingJobPreviousRunStatus.SUCCESS;
|
|
||||||
|
|
||||||
reportMailingJob.updatePreviousRunErrorLog(null);
|
|
||||||
|
|
||||||
if (errorLog != null && errorLog.length() > 0) {
|
|
||||||
reportMailingJobPreviousRunStatus = ReportMailingJobPreviousRunStatus.ERROR;
|
|
||||||
reportMailingJob.updatePreviousRunErrorLog(errorLog.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
reportMailingJob.increaseNumberOfRunsByOne();
|
|
||||||
reportMailingJob.updatePreviousRunStatus(reportMailingJobPreviousRunStatus.getValue());
|
|
||||||
reportMailingJob.updatePreviousRunDateTime(reportMailingJob.getNextRunDateTime());
|
|
||||||
|
|
||||||
// check if the job has a recurrence pattern, if not deactivate the job.
|
|
||||||
// The job will only run once
|
|
||||||
if (StringUtils.isEmpty(recurrence)) {
|
|
||||||
// deactivate job
|
|
||||||
reportMailingJob.deactivate();
|
|
||||||
|
|
||||||
// job will only run once, no next run time
|
|
||||||
reportMailingJob.updateNextRunDateTime(null);
|
|
||||||
} else if (nextRunDateTime != null) {
|
|
||||||
final LocalDateTime nextRecurringDateTime = this.createNextRecurringDateTime(recurrence, nextRunDateTime);
|
|
||||||
|
|
||||||
// finally update the next run date time property
|
|
||||||
reportMailingJob.updateNextRunDateTime(nextRecurringDateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the ReportMailingJob entity
|
|
||||||
this.reportMailingJobRepository.save(reportMailingJob);
|
|
||||||
|
|
||||||
// create a new report mailing job run history entity
|
|
||||||
this.createReportMailingJobRunHistroryAfterJobExecution(reportMailingJob, errorLog, jobStartDateTime,
|
|
||||||
reportMailingJobPreviousRunStatus.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create the next recurring LocalDateTime from recurrence pattern, start LocalDateTime and current DateTime
|
* create the next recurring LocalDateTime from recurrence pattern, start LocalDateTime and current DateTime
|
||||||
*
|
*
|
||||||
@@ -369,29 +225,6 @@ public class ReportMailingJobWritePlatformServiceImpl implements ReportMailingJo
|
|||||||
return nextRecurringDateTime;
|
return nextRecurringDateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* create a new report mailing job run history entity after job execution
|
|
||||||
*
|
|
||||||
* @param reportMailingJob
|
|
||||||
* -- the report mailing job entity
|
|
||||||
* @param errorLog
|
|
||||||
* -- StringBuilder object containing the error log if any
|
|
||||||
* @param jobStartDateTime
|
|
||||||
* -- the start LocalDateTime of the job
|
|
||||||
* @param jobRunStatus
|
|
||||||
* -- the status of the job (success/error)
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
private void createReportMailingJobRunHistroryAfterJobExecution(final ReportMailingJob reportMailingJob, final StringBuilder errorLog,
|
|
||||||
final LocalDateTime jobStartDateTime, final String jobRunStatus) {
|
|
||||||
final LocalDateTime jobEndDateTime = DateUtils.getLocalDateTimeOfTenant();
|
|
||||||
final String errorLogToString = (errorLog != null) ? errorLog.toString() : null;
|
|
||||||
final ReportMailingJobRunHistory reportMailingJobRunHistory = ReportMailingJobRunHistory.newInstance(reportMailingJob,
|
|
||||||
jobStartDateTime, jobEndDateTime, jobRunStatus, null, errorLogToString);
|
|
||||||
|
|
||||||
this.reportMailingJobRunHistoryRepository.save(reportMailingJobRunHistory);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle any SQL data integrity issue
|
* Handle any SQL data integrity issue
|
||||||
*
|
*
|
||||||
@@ -415,92 +248,4 @@ public class ReportMailingJobWritePlatformServiceImpl implements ReportMailingJo
|
|||||||
throw new PlatformDataIntegrityException("error.msg.charge.unknown.data.integrity.issue",
|
throw new PlatformDataIntegrityException("error.msg.charge.unknown.data.integrity.issue",
|
||||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* generate the report output stream
|
|
||||||
*
|
|
||||||
* @param reportMailingJob
|
|
||||||
* @param emailAttachmentFileFormat
|
|
||||||
* @param reportParams
|
|
||||||
* @param reportName
|
|
||||||
* @param errorLog
|
|
||||||
* @return the error log StringBuilder object
|
|
||||||
*/
|
|
||||||
private StringBuilder generateReportOutputStream(final ReportMailingJob reportMailingJob,
|
|
||||||
final ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat, final MultivaluedMap<String, String> reportParams,
|
|
||||||
final String reportName, final StringBuilder errorLog) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
final boolean isSelfServiceUserReport = false;
|
|
||||||
final String reportType = this.readReportingService.getReportType(reportName, isSelfServiceUserReport, false);
|
|
||||||
final ReportingProcessService reportingProcessService = this.reportingProcessServiceProvider
|
|
||||||
.findReportingProcessService(reportType);
|
|
||||||
|
|
||||||
if (reportingProcessService != null) {
|
|
||||||
final Response processReport = reportingProcessService.processRequest(reportName, reportParams);
|
|
||||||
final Object reponseObject = (processReport != null) ? processReport.getEntity() : null;
|
|
||||||
|
|
||||||
if (reponseObject != null && reponseObject.getClass().equals(ByteArrayOutputStream.class)) {
|
|
||||||
final ByteArrayOutputStream byteArrayOutputStream = ByteArrayOutputStream.class.cast(reponseObject);
|
|
||||||
final String fileLocation = FileSystemContentRepository.FINERACT_BASE_DIR + File.separator + "";
|
|
||||||
final String fileNameWithoutExtension = fileLocation + File.separator + reportName;
|
|
||||||
|
|
||||||
// check if file directory exists, if not create directory
|
|
||||||
if (!new File(fileLocation).isDirectory()) {
|
|
||||||
new File(fileLocation).mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((byteArrayOutputStream == null) || byteArrayOutputStream.size() == 0) {
|
|
||||||
errorLog.append("Report processing failed, empty output stream created");
|
|
||||||
} else if ((errorLog != null && errorLog.length() == 0) && (byteArrayOutputStream.size() > 0)) {
|
|
||||||
final String fileName = fileNameWithoutExtension + "." + emailAttachmentFileFormat.getValue();
|
|
||||||
|
|
||||||
// send the file to email recipients
|
|
||||||
this.sendReportFileToEmailRecipients(reportMailingJob, fileName, byteArrayOutputStream, errorLog);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errorLog.append("Response object entity is not equal to ByteArrayOutputStream ---------- ");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errorLog.append(ReportingProcessServiceProvider.SERVICE_MISSING + reportType);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
errorLog.append("The ReportMailingJobWritePlatformServiceImpl.generateReportOutputStream method threw an Exception: " + e
|
|
||||||
+ " ---------- ");
|
|
||||||
}
|
|
||||||
|
|
||||||
return errorLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* send report file to email recipients
|
|
||||||
*
|
|
||||||
* @param reportMailingJob
|
|
||||||
* @param fileName
|
|
||||||
* @param byteArrayOutputStream
|
|
||||||
* @param errorLog
|
|
||||||
*/
|
|
||||||
private void sendReportFileToEmailRecipients(final ReportMailingJob reportMailingJob, final String fileName,
|
|
||||||
final ByteArrayOutputStream byteArrayOutputStream, final StringBuilder errorLog) {
|
|
||||||
final Set<String> emailRecipients = this.reportMailingJobValidator.validateEmailRecipients(reportMailingJob.getEmailRecipients());
|
|
||||||
|
|
||||||
try {
|
|
||||||
final File file = new File(fileName);
|
|
||||||
final FileOutputStream outputStream = new FileOutputStream(file);
|
|
||||||
byteArrayOutputStream.writeTo(outputStream);
|
|
||||||
|
|
||||||
for (String emailRecipient : emailRecipients) {
|
|
||||||
final ReportMailingJobEmailData reportMailingJobEmailData = new ReportMailingJobEmailData(emailRecipient,
|
|
||||||
reportMailingJob.getEmailMessage(), reportMailingJob.getEmailSubject(), file);
|
|
||||||
|
|
||||||
this.reportMailingJobEmailService.sendEmailWithAttachment(reportMailingJobEmailData);
|
|
||||||
}
|
|
||||||
|
|
||||||
outputStream.close();
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
errorLog.append("The ReportMailingJobWritePlatformServiceImpl.sendReportFileToEmailRecipients method threw an IOException "
|
|
||||||
+ "exception: " + e + " ---------- ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-17
@@ -33,6 +33,7 @@ import javax.ws.rs.QueryParam;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.accounting.journalentry.api.DateParam;
|
import org.apache.fineract.accounting.journalentry.api.DateParam;
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||||
@@ -46,7 +47,6 @@ import org.apache.fineract.infrastructure.core.service.SearchParameters;
|
|||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.infrastructure.sms.data.SmsData;
|
import org.apache.fineract.infrastructure.sms.data.SmsData;
|
||||||
import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService;
|
import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -56,6 +56,7 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
@Scope("singleton")
|
@Scope("singleton")
|
||||||
@Tag(name = "SMS", description = "")
|
@Tag(name = "SMS", description = "")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SmsApiResource {
|
public class SmsApiResource {
|
||||||
|
|
||||||
private final String resourceNameForPermissions = "SMS";
|
private final String resourceNameForPermissions = "SMS";
|
||||||
@@ -66,17 +67,6 @@ public class SmsApiResource {
|
|||||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public SmsApiResource(final PlatformSecurityContext context, final SmsReadPlatformService readPlatformService,
|
|
||||||
final DefaultToApiJsonSerializer<SmsData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
|
|
||||||
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
|
|
||||||
this.context = context;
|
|
||||||
this.readPlatformService = readPlatformService;
|
|
||||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
|
||||||
this.apiRequestParameterHelper = apiRequestParameterHelper;
|
|
||||||
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
public String retrieveAll(@Context final UriInfo uriInfo) {
|
public String retrieveAll(@Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
@@ -117,7 +107,7 @@ public class SmsApiResource {
|
|||||||
@QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
|
@QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit,
|
||||||
@QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
|
@QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) {
|
||||||
|
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions);
|
||||||
|
|
||||||
final SearchParameters searchParameters = SearchParameters.forSMSCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
|
final SearchParameters searchParameters = SearchParameters.forSMSCampaign(sqlSearch, offset, limit, orderBy, sortOrder);
|
||||||
|
|
||||||
@@ -130,11 +120,11 @@ public class SmsApiResource {
|
|||||||
toDate = toDateParam.getDate("toDate", dateFormat, locale);
|
toDate = toDateParam.getDate("toDate", dateFormat, locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Page<SmsData> smsMessages = this.readPlatformService.retrieveSmsByStatus(campaignId, searchParameters, status.intValue(),
|
final Page<SmsData> smsMessages = readPlatformService.retrieveSmsByStatus(campaignId, searchParameters, status.intValue(), fromDate,
|
||||||
fromDate, toDate);
|
toDate);
|
||||||
|
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return this.toApiJsonSerializer.serialize(settings, smsMessages);
|
return toApiJsonSerializer.serialize(settings, smsMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
|
|||||||
-10
@@ -28,11 +28,6 @@ import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
|
|||||||
**/
|
**/
|
||||||
public interface SmsMessageScheduledJobService {
|
public interface SmsMessageScheduledJobService {
|
||||||
|
|
||||||
/**
|
|
||||||
* sends a batch of SMS messages to the SMS gateway
|
|
||||||
**/
|
|
||||||
void sendMessagesToGateway();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sends triggered batch SMS messages to SMS gateway
|
* sends triggered batch SMS messages to SMS gateway
|
||||||
*
|
*
|
||||||
@@ -49,9 +44,4 @@ public interface SmsMessageScheduledJobService {
|
|||||||
* the provider ID of the SMS gateway to be used
|
* the provider ID of the SMS gateway to be used
|
||||||
*/
|
*/
|
||||||
void sendTriggeredMessage(Collection<SmsMessage> smsMessage, long providerId);
|
void sendTriggeredMessage(Collection<SmsMessage> smsMessage, long providerId);
|
||||||
|
|
||||||
/**
|
|
||||||
* get delivery report from the SMS gateway
|
|
||||||
**/
|
|
||||||
void getDeliveryReports();
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-167
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.sms.scheduler;
|
package org.apache.fineract.infrastructure.sms.scheduler;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -28,34 +27,27 @@ import java.util.Map;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.campaigns.helper.SmsConfigUtils;
|
import org.apache.fineract.infrastructure.campaigns.helper.SmsConfigUtils;
|
||||||
import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignConstants;
|
import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignConstants;
|
||||||
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
|
import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
|
||||||
import org.apache.fineract.infrastructure.campaigns.sms.exception.ConnectionFailureException;
|
import org.apache.fineract.infrastructure.campaigns.sms.exception.ConnectionFailureException;
|
||||||
import org.apache.fineract.infrastructure.core.domain.FineractContext;
|
import org.apache.fineract.infrastructure.core.domain.FineractContext;
|
||||||
import org.apache.fineract.infrastructure.core.service.Page;
|
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.infrastructure.gcm.service.NotificationSenderService;
|
import org.apache.fineract.infrastructure.gcm.service.NotificationSenderService;
|
||||||
import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
|
|
||||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
|
||||||
import org.apache.fineract.infrastructure.sms.data.SmsMessageApiQueueResourceData;
|
import org.apache.fineract.infrastructure.sms.data.SmsMessageApiQueueResourceData;
|
||||||
import org.apache.fineract.infrastructure.sms.data.SmsMessageDeliveryReportData;
|
|
||||||
import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
|
||||||
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
|
||||||
import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
|
import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
|
||||||
import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.context.event.ContextClosedEvent;
|
import org.springframework.context.event.ContextClosedEvent;
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,89 +55,22 @@ import org.springframework.web.client.RestTemplate;
|
|||||||
**/
|
**/
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJobService {
|
public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJobService {
|
||||||
|
|
||||||
private final SmsMessageRepository smsMessageRepository;
|
private final SmsMessageRepository smsMessageRepository;
|
||||||
private final SmsReadPlatformService smsReadPlatformService;
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
private final SmsConfigUtils smsConfigUtils;
|
private final SmsConfigUtils smsConfigUtils;
|
||||||
private final NotificationSenderService notificationSenderService;
|
private final NotificationSenderService notificationSenderService;
|
||||||
private ExecutorService genericExecutorService;
|
private ExecutorService genericExecutorService;
|
||||||
private ExecutorService triggeredExecutorService;
|
private ExecutorService triggeredExecutorService;
|
||||||
|
|
||||||
/**
|
|
||||||
* SmsMessageScheduledJobServiceImpl constructor
|
|
||||||
**/
|
|
||||||
@Autowired
|
|
||||||
public SmsMessageScheduledJobServiceImpl(SmsMessageRepository smsMessageRepository, SmsReadPlatformService smsReadPlatformService,
|
|
||||||
final SmsConfigUtils smsConfigUtils, final NotificationSenderService notificationSenderService) {
|
|
||||||
this.smsMessageRepository = smsMessageRepository;
|
|
||||||
this.smsReadPlatformService = smsReadPlatformService;
|
|
||||||
this.smsConfigUtils = smsConfigUtils;
|
|
||||||
this.notificationSenderService = notificationSenderService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void initializeExecutorService() {
|
public void initializeExecutorService() {
|
||||||
genericExecutorService = Executors.newSingleThreadExecutor();
|
genericExecutorService = Executors.newSingleThreadExecutor();
|
||||||
triggeredExecutorService = Executors.newSingleThreadExecutor();
|
triggeredExecutorService = Executors.newSingleThreadExecutor();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send batches of SMS messages to the SMS gateway (or intermediate gateway)
|
|
||||||
**/
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
@CronTarget(jobName = JobName.SEND_MESSAGES_TO_SMS_GATEWAY)
|
|
||||||
public void sendMessagesToGateway() {
|
|
||||||
Integer pageLimit = 200;
|
|
||||||
Integer page = 0;
|
|
||||||
int totalRecords = 0;
|
|
||||||
do {
|
|
||||||
PageRequest pageRequest = PageRequest.of(0, pageLimit);
|
|
||||||
org.springframework.data.domain.Page<SmsMessage> pendingMessages = this.smsMessageRepository
|
|
||||||
.findByStatusType(SmsMessageStatusType.PENDING.getValue(), pageRequest);
|
|
||||||
List<SmsMessage> toSaveMessages = new ArrayList<>();
|
|
||||||
List<SmsMessage> toSendNotificationMessages = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (pendingMessages.getContent().size() > 0) {
|
|
||||||
final String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
|
|
||||||
Iterator<SmsMessage> pendingMessageIterator = pendingMessages.iterator();
|
|
||||||
Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas = new ArrayList<>();
|
|
||||||
while (pendingMessageIterator.hasNext()) {
|
|
||||||
SmsMessage smsData = pendingMessageIterator.next();
|
|
||||||
if (smsData.isNotification()) {
|
|
||||||
smsData.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
|
|
||||||
toSendNotificationMessages.add(smsData);
|
|
||||||
} else {
|
|
||||||
SmsMessageApiQueueResourceData apiQueueResourceData = SmsMessageApiQueueResourceData.instance(smsData.getId(),
|
|
||||||
tenantIdentifier, null, null, smsData.getMobileNo(), smsData.getMessage(),
|
|
||||||
smsData.getSmsCampaign().getProviderId());
|
|
||||||
apiQueueResourceDatas.add(apiQueueResourceData);
|
|
||||||
smsData.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
|
|
||||||
toSaveMessages.add(smsData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (toSaveMessages.size() > 0) {
|
|
||||||
this.smsMessageRepository.saveAll(toSaveMessages);
|
|
||||||
this.smsMessageRepository.flush();
|
|
||||||
this.genericExecutorService.execute(new SmsTask(apiQueueResourceDatas, ThreadLocalContextUtil.getContext()));
|
|
||||||
}
|
|
||||||
if (!toSendNotificationMessages.isEmpty()) {
|
|
||||||
this.notificationSenderService.sendNotification(toSendNotificationMessages);
|
|
||||||
}
|
|
||||||
// new MyThread(ThreadLocalContextUtil.getTenant(),
|
|
||||||
// apiQueueResourceDatas).start();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ConnectionFailureException(SmsCampaignConstants.SMS, e);
|
|
||||||
}
|
|
||||||
page++;
|
|
||||||
totalRecords = pendingMessages.getTotalPages();
|
|
||||||
} while (page < totalRecords);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void connectAndSendToIntermediateServer(Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas) {
|
private void connectAndSendToIntermediateServer(Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas) {
|
||||||
Map<String, Object> hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("sms",
|
Map<String, Object> hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("sms",
|
||||||
SmsMessageApiQueueResourceData.toJsonString(apiQueueResourceDatas));
|
SmsMessageApiQueueResourceData.toJsonString(apiQueueResourceDatas));
|
||||||
@@ -172,7 +97,6 @@ public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJob
|
|||||||
for (Map.Entry<SmsCampaign, Collection<SmsMessage>> entry : smsDataMap.entrySet()) {
|
for (Map.Entry<SmsCampaign, Collection<SmsMessage>> entry : smsDataMap.entrySet()) {
|
||||||
Iterator<SmsMessage> smsMessageIterator = entry.getValue().iterator();
|
Iterator<SmsMessage> smsMessageIterator = entry.getValue().iterator();
|
||||||
Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas = new ArrayList<>();
|
Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas = new ArrayList<>();
|
||||||
StringBuilder request = new StringBuilder();
|
|
||||||
while (smsMessageIterator.hasNext()) {
|
while (smsMessageIterator.hasNext()) {
|
||||||
SmsMessage smsMessage = smsMessageIterator.next();
|
SmsMessage smsMessage = smsMessageIterator.next();
|
||||||
if (smsMessage.isNotification()) {
|
if (smsMessage.isNotification()) {
|
||||||
@@ -223,95 +147,6 @@ public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJob
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* get SMS message delivery reports from the SMS gateway (or intermediate gateway)
|
|
||||||
**/
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
@CronTarget(jobName = JobName.GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY)
|
|
||||||
public void getDeliveryReports() {
|
|
||||||
int page = 0;
|
|
||||||
int totalRecords = 0;
|
|
||||||
Integer limit = 200;
|
|
||||||
do {
|
|
||||||
Page<Long> smsMessageInternalIds = this.smsReadPlatformService.retrieveAllWaitingForDeliveryReport(limit);
|
|
||||||
// only proceed if there are sms message with status type enum 300
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (smsMessageInternalIds.getPageItems().size() > 0) {
|
|
||||||
// make request
|
|
||||||
Map<String, Object> hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("sms/report",
|
|
||||||
new Gson().toJson(smsMessageInternalIds.getPageItems()));
|
|
||||||
URI uri = (URI) hostConfig.get("uri");
|
|
||||||
HttpEntity<?> entity = (HttpEntity<?>) hostConfig.get("entity");
|
|
||||||
ResponseEntity<Collection<SmsMessageDeliveryReportData>> responseOne = restTemplate.exchange(uri, HttpMethod.POST,
|
|
||||||
entity, new ParameterizedTypeReference<Collection<SmsMessageDeliveryReportData>>() {
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
Collection<SmsMessageDeliveryReportData> smsMessageDeliveryReportDatas = responseOne.getBody();
|
|
||||||
Iterator<SmsMessageDeliveryReportData> responseReportIterator = smsMessageDeliveryReportDatas.iterator();
|
|
||||||
while (responseReportIterator.hasNext()) {
|
|
||||||
SmsMessageDeliveryReportData smsMessageDeliveryReportData = responseReportIterator.next();
|
|
||||||
Integer deliveryStatus = smsMessageDeliveryReportData.getDeliveryStatus();
|
|
||||||
|
|
||||||
if (!smsMessageDeliveryReportData.getHasError() && deliveryStatus != 100) {
|
|
||||||
SmsMessage smsMessage = this.smsMessageRepository.findById(smsMessageDeliveryReportData.getId()).orElse(null);
|
|
||||||
Integer statusType = smsMessage.getStatusType();
|
|
||||||
|
|
||||||
switch (deliveryStatus) {
|
|
||||||
case 0:
|
|
||||||
statusType = SmsMessageStatusType.INVALID.getValue();
|
|
||||||
break;
|
|
||||||
case 150:
|
|
||||||
statusType = SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue();
|
|
||||||
break;
|
|
||||||
case 200:
|
|
||||||
statusType = SmsMessageStatusType.SENT.getValue();
|
|
||||||
break;
|
|
||||||
case 300:
|
|
||||||
statusType = SmsMessageStatusType.DELIVERED.getValue();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 400:
|
|
||||||
statusType = SmsMessageStatusType.FAILED.getValue();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
statusType = smsMessage.getStatusType();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean statusChanged = !statusType.equals(smsMessage.getStatusType());
|
|
||||||
|
|
||||||
// update the status Type enum
|
|
||||||
smsMessage.setStatusType(statusType);
|
|
||||||
|
|
||||||
// update the externalId
|
|
||||||
smsMessage.setExternalId(smsMessageDeliveryReportData.getExternalId());
|
|
||||||
|
|
||||||
// save the SmsMessage entity
|
|
||||||
this.smsMessageRepository.save(smsMessage);
|
|
||||||
|
|
||||||
if (statusChanged) {
|
|
||||||
log.info("Status of SMS message id: {} successfully changed to {}", smsMessage.getId(), statusType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (smsMessageDeliveryReportDatas.size() > 0) {
|
|
||||||
log.info("{} delivery report(s) successfully received from the intermediate gateway - sms",
|
|
||||||
smsMessageDeliveryReportDatas.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Error occured.", e);
|
|
||||||
}
|
|
||||||
page++;
|
|
||||||
totalRecords = smsMessageInternalIds.getTotalFilteredRecords();
|
|
||||||
} while (page < totalRecords);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SmsTask implements Runnable, ApplicationListener<ContextClosedEvent> {
|
class SmsTask implements Runnable, ApplicationListener<ContextClosedEvent> {
|
||||||
|
|
||||||
private final FineractContext context;
|
private final FineractContext context;
|
||||||
|
|||||||
+1
-3
@@ -168,9 +168,7 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService {
|
|||||||
final String sqlPlusLimit = limit > 0 ? " " + sqlGenerator.limit(limit) : "";
|
final String sqlPlusLimit = limit > 0 ? " " + sqlGenerator.limit(limit) : "";
|
||||||
final String sql = "select id from " + this.smsRowMapper.tableName() + " where status_enum = "
|
final String sql = "select id from " + this.smsRowMapper.tableName() + " where status_enum = "
|
||||||
+ SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue() + sqlPlusLimit;
|
+ SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue() + sqlPlusLimit;
|
||||||
return this.paginationHelper.fetchPage(jdbcTemplate, sql, Long.class);
|
return paginationHelper.fetchPage(jdbcTemplate, sql, Long.class);
|
||||||
// (this.jdbcTemplate, sqlCountRows, new Object [] {}, Long.class);
|
|
||||||
// this.jdbcTemplate.queryForList(sql, Long.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* 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.account.jobs.executestandinginstructions;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||||
|
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ExecuteStandingInstructionsConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||||
|
@Autowired
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
@Autowired
|
||||||
|
private DatabaseSpecificSQLGenerator sqlGenerator;
|
||||||
|
@Autowired
|
||||||
|
private AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step executeStandingInstructionsStep() {
|
||||||
|
return steps.get(JobName.EXECUTE_STANDING_INSTRUCTIONS.name()).tasklet(executeStandingInstructionsTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job executeStandingInstructionsJob() {
|
||||||
|
return jobs.get(JobName.EXECUTE_STANDING_INSTRUCTIONS.name()).start(executeStandingInstructionsStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ExecuteStandingInstructionsTasklet executeStandingInstructionsTasklet() {
|
||||||
|
return new ExecuteStandingInstructionsTasklet(standingInstructionReadPlatformService, jdbcTemplate, sqlGenerator,
|
||||||
|
accountTransfersWritePlatformService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* 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.account.jobs.executestandinginstructions;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||||
|
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
||||||
|
import org.apache.fineract.portfolio.account.data.StandingInstructionData;
|
||||||
|
import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData;
|
||||||
|
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||||
|
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||||
|
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||||
|
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||||
|
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||||
|
import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ExecuteStandingInstructionsTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||||
|
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
Collection<StandingInstructionData> instructionData = standingInstructionReadPlatformService
|
||||||
|
.retrieveAll(StandingInstructionStatus.ACTIVE.getValue());
|
||||||
|
List<Throwable> errors = new ArrayList<>();
|
||||||
|
for (StandingInstructionData data : instructionData) {
|
||||||
|
boolean isDueForTransfer = false;
|
||||||
|
AccountTransferRecurrenceType recurrenceType = data.recurrenceType();
|
||||||
|
StandingInstructionType instructionType = data.instructionType();
|
||||||
|
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||||
|
if (recurrenceType.isPeriodicRecurrence()) {
|
||||||
|
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
||||||
|
PeriodFrequencyType frequencyType = data.recurrenceFrequency();
|
||||||
|
LocalDate startDate = data.validFrom();
|
||||||
|
if (frequencyType.isMonthly()) {
|
||||||
|
startDate = startDate.withDayOfMonth(data.recurrenceOnDay());
|
||||||
|
if (startDate.isBefore(data.validFrom())) {
|
||||||
|
startDate = startDate.plusMonths(1);
|
||||||
|
}
|
||||||
|
} else if (frequencyType.isYearly()) {
|
||||||
|
startDate = startDate.withDayOfMonth(data.recurrenceOnDay()).withMonth(data.recurrenceOnMonth());
|
||||||
|
if (startDate.isBefore(data.validFrom())) {
|
||||||
|
startDate = startDate.plusYears(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isDueForTransfer = scheduledDateGenerator.isDateFallsInSchedule(frequencyType, data.recurrenceInterval(), startDate,
|
||||||
|
transactionDate);
|
||||||
|
|
||||||
|
}
|
||||||
|
BigDecimal transactionAmount = data.amount();
|
||||||
|
if (data.toAccountType().isLoanAccount()
|
||||||
|
&& (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) {
|
||||||
|
StandingInstructionDuesData standingInstructionDuesData = standingInstructionReadPlatformService
|
||||||
|
.retriveLoanDuesData(data.toAccount().accountId());
|
||||||
|
if (data.instructionType().isDuesAmoutTransfer()) {
|
||||||
|
transactionAmount = standingInstructionDuesData.totalDueAmount();
|
||||||
|
}
|
||||||
|
if (recurrenceType.isDuesRecurrence()) {
|
||||||
|
isDueForTransfer = LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).equals(standingInstructionDuesData.dueDate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDueForTransfer && transactionAmount != null && transactionAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
final SavingsAccount fromSavingsAccount = null;
|
||||||
|
final boolean isRegularTransaction = true;
|
||||||
|
final boolean isExceptionForBalanceCheck = false;
|
||||||
|
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount, data.fromAccountType(),
|
||||||
|
data.toAccountType(), data.fromAccount().accountId(), data.toAccount().accountId(),
|
||||||
|
data.name() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null,
|
||||||
|
data.transferType().getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction,
|
||||||
|
isExceptionForBalanceCheck);
|
||||||
|
final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId());
|
||||||
|
|
||||||
|
if (transferCompleted) {
|
||||||
|
final String updateQuery = "UPDATE m_account_transfer_standing_instructions SET last_run_date = ? where id = ?";
|
||||||
|
jdbcTemplate.update(updateQuery, transactionDate, data.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
throw new JobExecutionException(errors);
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean transferAmount(final List<Throwable> errors, final AccountTransferDTO accountTransferDTO, final Long instructionId) {
|
||||||
|
boolean transferCompleted = true;
|
||||||
|
StringBuilder errorLog = new StringBuilder();
|
||||||
|
StringBuilder updateQuery = new StringBuilder(
|
||||||
|
"INSERT INTO m_account_transfer_standing_instructions_history (standing_instruction_id, " + sqlGenerator.escape("status")
|
||||||
|
+ ", amount,execution_time, error_log) VALUES (");
|
||||||
|
try {
|
||||||
|
accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||||
|
} catch (final PlatformApiDataValidationException e) {
|
||||||
|
errors.add(new Exception("Validation exception while transfering funds for standing Instruction id" + instructionId + " from "
|
||||||
|
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||||
|
errorLog.append("Validation exception while trasfering funds ").append(e.getDefaultUserMessage());
|
||||||
|
} catch (final InsufficientAccountBalanceException e) {
|
||||||
|
errors.add(new Exception("InsufficientAccountBalance Exception while trasfering funds for standing Instruction id"
|
||||||
|
+ instructionId + " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||||
|
errorLog.append("InsufficientAccountBalance Exception ");
|
||||||
|
} catch (final AbstractPlatformServiceUnavailableException e) {
|
||||||
|
errors.add(new Exception("Platform exception while trasfering funds for standing Instruction id" + instructionId + " from "
|
||||||
|
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||||
|
errorLog.append("Platform exception while trasfering funds ").append(e.getDefaultUserMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
errors.add(new Exception("Unhandled System Exception while trasfering funds for standing Instruction id" + instructionId
|
||||||
|
+ " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||||
|
errorLog.append("Exception while trasfering funds ").append(e.getMessage());
|
||||||
|
|
||||||
|
}
|
||||||
|
updateQuery.append(instructionId).append(",");
|
||||||
|
if (errorLog.length() > 0) {
|
||||||
|
transferCompleted = false;
|
||||||
|
updateQuery.append("'failed'").append(",");
|
||||||
|
} else {
|
||||||
|
updateQuery.append("'success'").append(",");
|
||||||
|
}
|
||||||
|
updateQuery.append(accountTransferDTO.getTransactionAmount().doubleValue());
|
||||||
|
updateQuery.append(", now(),");
|
||||||
|
updateQuery.append("'").append(errorLog).append("')");
|
||||||
|
jdbcTemplate.update(updateQuery.toString());
|
||||||
|
return transferCompleted;
|
||||||
|
}
|
||||||
|
}
|
||||||
-3
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.account.service;
|
|||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
|
||||||
|
|
||||||
public interface StandingInstructionWritePlatformService {
|
public interface StandingInstructionWritePlatformService {
|
||||||
|
|
||||||
@@ -28,8 +27,6 @@ public interface StandingInstructionWritePlatformService {
|
|||||||
|
|
||||||
CommandProcessingResult update(Long id, JsonCommand command);
|
CommandProcessingResult update(Long id, JsonCommand command);
|
||||||
|
|
||||||
void executeStandingInstructions() throws JobExecutionException;
|
|
||||||
|
|
||||||
CommandProcessingResult delete(Long id);
|
CommandProcessingResult delete(Long id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-157
@@ -23,85 +23,39 @@ import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromC
|
|||||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
|
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
|
||||||
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.portfolio.account.PortfolioAccountType;
|
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||||
import org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
|
import org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
|
||||||
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
|
||||||
import org.apache.fineract.portfolio.account.data.StandingInstructionData;
|
|
||||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
||||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData;
|
|
||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
|
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
|
||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
|
||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction;
|
import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction;
|
||||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionAssembler;
|
import org.apache.fineract.portfolio.account.domain.StandingInstructionAssembler;
|
||||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
|
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
|
||||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
|
||||||
import org.apache.fineract.portfolio.account.exception.StandingInstructionNotFoundException;
|
import org.apache.fineract.portfolio.account.exception.StandingInstructionNotFoundException;
|
||||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
|
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
|
||||||
import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.dao.NonTransientDataAccessException;
|
import org.springframework.dao.NonTransientDataAccessException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class StandingInstructionWritePlatformServiceImpl implements StandingInstructionWritePlatformService {
|
public class StandingInstructionWritePlatformServiceImpl implements StandingInstructionWritePlatformService {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(StandingInstructionWritePlatformServiceImpl.class);
|
|
||||||
|
|
||||||
private final StandingInstructionDataValidator standingInstructionDataValidator;
|
private final StandingInstructionDataValidator standingInstructionDataValidator;
|
||||||
private final StandingInstructionAssembler standingInstructionAssembler;
|
private final StandingInstructionAssembler standingInstructionAssembler;
|
||||||
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
||||||
private final StandingInstructionRepository standingInstructionRepository;
|
private final StandingInstructionRepository standingInstructionRepository;
|
||||||
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
|
||||||
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
|
||||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public StandingInstructionWritePlatformServiceImpl(final StandingInstructionDataValidator standingInstructionDataValidator,
|
|
||||||
final StandingInstructionAssembler standingInstructionAssembler,
|
|
||||||
final AccountTransferDetailRepository accountTransferDetailRepository,
|
|
||||||
final StandingInstructionRepository standingInstructionRepository,
|
|
||||||
final StandingInstructionReadPlatformService standingInstructionReadPlatformService,
|
|
||||||
final AccountTransfersWritePlatformService accountTransfersWritePlatformService, final JdbcTemplate jdbcTemplate,
|
|
||||||
DatabaseSpecificSQLGenerator sqlGenerator) {
|
|
||||||
this.standingInstructionDataValidator = standingInstructionDataValidator;
|
|
||||||
this.standingInstructionAssembler = standingInstructionAssembler;
|
|
||||||
this.accountTransferDetailRepository = accountTransferDetailRepository;
|
|
||||||
this.standingInstructionRepository = standingInstructionRepository;
|
|
||||||
this.standingInstructionReadPlatformService = standingInstructionReadPlatformService;
|
|
||||||
this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
|
|
||||||
this.jdbcTemplate = jdbcTemplate;
|
|
||||||
this.sqlGenerator = sqlGenerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -152,7 +106,7 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst
|
|||||||
throw new PlatformDataIntegrityException("error.msg.standinginstruction.duplicate.name",
|
throw new PlatformDataIntegrityException("error.msg.standinginstruction.duplicate.name",
|
||||||
"Standinginstruction with name `" + name + "` already exists", "name", name);
|
"Standinginstruction with name `" + name + "` already exists", "name", name);
|
||||||
}
|
}
|
||||||
LOG.error("Error occured.", dve);
|
log.error("Error occured.", dve);
|
||||||
throw new PlatformDataIntegrityException("error.msg.client.unknown.data.integrity.issue",
|
throw new PlatformDataIntegrityException("error.msg.client.unknown.data.integrity.issue",
|
||||||
"Unknown data integrity issue with resource.");
|
"Unknown data integrity issue with resource.");
|
||||||
}
|
}
|
||||||
@@ -190,110 +144,4 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst
|
|||||||
actualChanges.put(statusParamName, StandingInstructionStatus.DELETED.getValue());
|
actualChanges.put(statusParamName, StandingInstructionStatus.DELETED.getValue());
|
||||||
return new CommandProcessingResultBuilder().withEntityId(id).with(actualChanges).build();
|
return new CommandProcessingResultBuilder().withEntityId(id).with(actualChanges).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.EXECUTE_STANDING_INSTRUCTIONS)
|
|
||||||
public void executeStandingInstructions() throws JobExecutionException {
|
|
||||||
Collection<StandingInstructionData> instructionDatas = this.standingInstructionReadPlatformService
|
|
||||||
.retrieveAll(StandingInstructionStatus.ACTIVE.getValue());
|
|
||||||
List<Throwable> errors = new ArrayList<>();
|
|
||||||
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
|
||||||
for (StandingInstructionData data : instructionDatas) {
|
|
||||||
boolean isDueForTransfer = false;
|
|
||||||
AccountTransferRecurrenceType recurrenceType = data.recurrenceType();
|
|
||||||
StandingInstructionType instructionType = data.instructionType();
|
|
||||||
if (recurrenceType.isPeriodicRecurrence()) {
|
|
||||||
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
|
||||||
PeriodFrequencyType frequencyType = data.recurrenceFrequency();
|
|
||||||
LocalDate startDate = data.validFrom();
|
|
||||||
if (frequencyType.isMonthly()) {
|
|
||||||
startDate = startDate.withDayOfMonth(data.recurrenceOnDay());
|
|
||||||
if (startDate.isBefore(data.validFrom())) {
|
|
||||||
startDate = startDate.plusMonths(1);
|
|
||||||
}
|
|
||||||
} else if (frequencyType.isYearly()) {
|
|
||||||
startDate = startDate.withDayOfMonth(data.recurrenceOnDay()).withMonth(data.recurrenceOnMonth());
|
|
||||||
if (startDate.isBefore(data.validFrom())) {
|
|
||||||
startDate = startDate.plusYears(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isDueForTransfer = scheduledDateGenerator.isDateFallsInSchedule(frequencyType, data.recurrenceInterval(), startDate,
|
|
||||||
transactionDate);
|
|
||||||
|
|
||||||
}
|
|
||||||
BigDecimal transactionAmount = data.amount();
|
|
||||||
if (data.toAccountType().isLoanAccount()
|
|
||||||
&& (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) {
|
|
||||||
StandingInstructionDuesData standingInstructionDuesData = this.standingInstructionReadPlatformService
|
|
||||||
.retriveLoanDuesData(data.toAccount().accountId());
|
|
||||||
if (data.instructionType().isDuesAmoutTransfer()) {
|
|
||||||
transactionAmount = standingInstructionDuesData.totalDueAmount();
|
|
||||||
}
|
|
||||||
if (recurrenceType.isDuesRecurrence()) {
|
|
||||||
isDueForTransfer = DateUtils.getBusinessLocalDate().equals(standingInstructionDuesData.dueDate());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDueForTransfer && transactionAmount != null && transactionAmount.compareTo(BigDecimal.ZERO) > 0) {
|
|
||||||
final SavingsAccount fromSavingsAccount = null;
|
|
||||||
final boolean isRegularTransaction = true;
|
|
||||||
final boolean isExceptionForBalanceCheck = false;
|
|
||||||
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount, data.fromAccountType(),
|
|
||||||
data.toAccountType(), data.fromAccount().accountId(), data.toAccount().accountId(),
|
|
||||||
data.name() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null,
|
|
||||||
data.transferType().getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction,
|
|
||||||
isExceptionForBalanceCheck);
|
|
||||||
final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId());
|
|
||||||
|
|
||||||
if (transferCompleted) {
|
|
||||||
final String updateQuery = "UPDATE m_account_transfer_standing_instructions SET last_run_date = ? where id = ?";
|
|
||||||
this.jdbcTemplate.update(updateQuery, transactionDate, data.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
throw new JobExecutionException(errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean transferAmount(final List<Throwable> errors, final AccountTransferDTO accountTransferDTO, final Long instructionId) {
|
|
||||||
boolean transferCompleted = true;
|
|
||||||
StringBuilder errorLog = new StringBuilder();
|
|
||||||
StringBuilder updateQuery = new StringBuilder(
|
|
||||||
"INSERT INTO m_account_transfer_standing_instructions_history (standing_instruction_id, " + sqlGenerator.escape("status")
|
|
||||||
+ ", amount, execution_time, error_log) VALUES (");
|
|
||||||
try {
|
|
||||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
|
||||||
} catch (final PlatformApiDataValidationException e) {
|
|
||||||
errors.add(new Exception("Validation exception while transfering funds for standing Instruction id" + instructionId + " from "
|
|
||||||
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
|
||||||
errorLog.append("Validation exception while trasfering funds " + e.getDefaultUserMessage());
|
|
||||||
} catch (final InsufficientAccountBalanceException e) {
|
|
||||||
errors.add(new Exception("InsufficientAccountBalance Exception while trasfering funds for standing Instruction id"
|
|
||||||
+ instructionId + " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
|
||||||
errorLog.append("InsufficientAccountBalance Exception ");
|
|
||||||
} catch (final AbstractPlatformServiceUnavailableException e) {
|
|
||||||
errors.add(new Exception("Platform exception while trasfering funds for standing Instruction id" + instructionId + " from "
|
|
||||||
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
|
||||||
errorLog.append("Platform exception while trasfering funds " + e.getDefaultUserMessage());
|
|
||||||
} catch (Exception e) {
|
|
||||||
errors.add(new Exception("Unhandled System Exception while trasfering funds for standing Instruction id" + instructionId
|
|
||||||
+ " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
|
||||||
errorLog.append("Exception while trasfering funds " + e.getMessage());
|
|
||||||
|
|
||||||
}
|
|
||||||
updateQuery.append(instructionId).append(",");
|
|
||||||
if (errorLog.length() > 0) {
|
|
||||||
transferCompleted = false;
|
|
||||||
updateQuery.append("'failed'").append(",");
|
|
||||||
} else {
|
|
||||||
updateQuery.append("'success'").append(",");
|
|
||||||
}
|
|
||||||
updateQuery.append(accountTransferDTO.getTransactionAmount().doubleValue());
|
|
||||||
updateQuery.append(", ").append(sqlGenerator.currentTenantDateTime()).append(" ");
|
|
||||||
updateQuery.append(", '").append(errorLog).append("')");
|
|
||||||
this.jdbcTemplate.update(updateQuery.toString());
|
|
||||||
return transferCompleted;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.addaccrualentries;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class AddAccrualEntriesConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step addAccrualEntriesStep(AddAccrualEntriesTasklet addAccrualEntriesTasklet) {
|
||||||
|
return steps.get(JobName.ADD_ACCRUAL_ENTRIES.name()).tasklet(addAccrualEntriesTasklet).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job addAccrualEntriesJob(AddAccrualEntriesTasklet addAccrualEntriesTasklet) {
|
||||||
|
return jobs.get(JobName.ADD_ACCRUAL_ENTRIES.name()).start(addAccrualEntriesStep(addAccrualEntriesTasklet))
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.addaccrualentries;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualWritePlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
public class AddAccrualEntriesTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final LoanReadPlatformService loanReadPlatformService;
|
||||||
|
private final LoanAccrualWritePlatformService loanAccrualWritePlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = loanReadPlatformService.retriveScheduleAccrualData();
|
||||||
|
Map<Long, Collection<LoanScheduleAccrualData>> loanDataMap = new HashMap<>();
|
||||||
|
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
|
||||||
|
if (loanDataMap.containsKey(accrualData.getLoanId())) {
|
||||||
|
loanDataMap.get(accrualData.getLoanId()).add(accrualData);
|
||||||
|
} else {
|
||||||
|
Collection<LoanScheduleAccrualData> accrualDatas = new ArrayList<>();
|
||||||
|
accrualDatas.add(accrualData);
|
||||||
|
loanDataMap.put(accrualData.getLoanId(), accrualDatas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Throwable> errors = new ArrayList<>();
|
||||||
|
for (Map.Entry<Long, Collection<LoanScheduleAccrualData>> mapEntry : loanDataMap.entrySet()) {
|
||||||
|
try {
|
||||||
|
loanAccrualWritePlatformService.addAccrualAccounting(mapEntry.getKey(), mapEntry.getValue());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to add accural transaction for loan {}", mapEntry.getKey(), e);
|
||||||
|
errors.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
throw new JobExecutionException(errors);
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.addperiodicaccrualentries;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class AddPeriodicAccrualEntriesConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private LoanAccrualPlatformService loanAccrualPlatformService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step addPeriodicAccrualEntriesStep() {
|
||||||
|
return steps.get(JobName.ADD_PERIODIC_ACCRUAL_ENTRIES.name()).tasklet(addPeriodicAccrualEntriesTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job addPeriodicAccrualEntriesJob() {
|
||||||
|
return jobs.get(JobName.ADD_PERIODIC_ACCRUAL_ENTRIES.name()).start(addPeriodicAccrualEntriesStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AddPeriodicAccrualEntriesTasklet addPeriodicAccrualEntriesTasklet() {
|
||||||
|
return new AddPeriodicAccrualEntriesTasklet(loanAccrualPlatformService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.addperiodicaccrualentries;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AddPeriodicAccrualEntriesTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final LoanAccrualPlatformService loanAccrualPlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
try {
|
||||||
|
addPeriodicAccruals(DateUtils.getBusinessLocalDate());
|
||||||
|
} catch (MultiException e) {
|
||||||
|
throw new JobExecutionException(e);
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addPeriodicAccruals(final LocalDate tilldate) throws MultiException {
|
||||||
|
loanAccrualPlatformService.addPeriodicAccruals(tilldate);
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.addperiodicaccrualentriesforloanswithincomepostedastransactions;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualWritePlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class AddPeriodicAccrualEntriesForLoansConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private LoanReadPlatformService loanReadPlatformService;
|
||||||
|
@Autowired
|
||||||
|
private LoanAccrualWritePlatformService loanAccrualWritePlatformService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step addPeriodicAccrualEntriesForLoansWithIncomePostedAsTransactionsStep() {
|
||||||
|
return steps.get(JobName.ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS.name())
|
||||||
|
.tasklet(addPeriodicAccrualEntriesForLoansWithIncomePostedAsTransactionsTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job addPeriodicAccrualEntriesForLoansWithIncomePostedAsTransactionsJob() {
|
||||||
|
return jobs.get(JobName.ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS.name())
|
||||||
|
.start(addPeriodicAccrualEntriesForLoansWithIncomePostedAsTransactionsStep()).incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AddPeriodicAccrualEntriesForLoansTasklet addPeriodicAccrualEntriesForLoansWithIncomePostedAsTransactionsTasklet() {
|
||||||
|
return new AddPeriodicAccrualEntriesForLoansTasklet(loanReadPlatformService, loanAccrualWritePlatformService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.addperiodicaccrualentriesforloanswithincomepostedastransactions;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualWritePlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AddPeriodicAccrualEntriesForLoansTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final LoanReadPlatformService loanReadPlatformService;
|
||||||
|
private final LoanAccrualWritePlatformService loanAccrualWritePlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
Collection<Long> loanIds = loanReadPlatformService.retrieveLoanIdsWithPendingIncomePostingTransactions();
|
||||||
|
if (!CollectionUtils.isEmpty(loanIds)) {
|
||||||
|
List<Throwable> errors = new ArrayList<>();
|
||||||
|
for (Long loanId : loanIds) {
|
||||||
|
try {
|
||||||
|
loanAccrualWritePlatformService.addIncomeAndAccrualTransactions(loanId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to add income and accrual transaction for loan {}", loanId, e);
|
||||||
|
errors.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
throw new JobExecutionException(errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.applychargetooverdueloaninstallment;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ApplyChargeToOverdueLoanInstallmentConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationDomainService configurationDomainService;
|
||||||
|
@Autowired
|
||||||
|
private LoanReadPlatformService loanReadPlatformService;
|
||||||
|
@Autowired
|
||||||
|
private LoanWritePlatformService loanWritePlatformService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step applyChargeToOverdueLoanInstallmentStep() {
|
||||||
|
return steps.get(JobName.APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT.name()).tasklet(applyChargeToOverdueLoanInstallmentTasklet())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job applyChargeToOverdueLoanInstallmentsJob() {
|
||||||
|
return jobs.get(JobName.APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT.name()).start(applyChargeToOverdueLoanInstallmentStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApplyChargeToOverdueLoanInstallmentTasklet applyChargeToOverdueLoanInstallmentTasklet() {
|
||||||
|
return new ApplyChargeToOverdueLoanInstallmentTasklet(configurationDomainService, loanReadPlatformService,
|
||||||
|
loanWritePlatformService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.applychargetooverdueloaninstallment;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ApplyChargeToOverdueLoanInstallmentTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final ConfigurationDomainService configurationDomainService;
|
||||||
|
private final LoanReadPlatformService loanReadPlatformService;
|
||||||
|
private final LoanWritePlatformService loanWritePlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
final Long penaltyWaitPeriodValue = configurationDomainService.retrievePenaltyWaitPeriod();
|
||||||
|
final Boolean backdatePenalties = configurationDomainService.isBackdatePenaltiesEnabled();
|
||||||
|
final Collection<OverdueLoanScheduleData> overdueLoanScheduledInstallments = loanReadPlatformService
|
||||||
|
.retrieveAllLoansWithOverdueInstallments(penaltyWaitPeriodValue, backdatePenalties);
|
||||||
|
|
||||||
|
if (!overdueLoanScheduledInstallments.isEmpty()) {
|
||||||
|
final Map<Long, Collection<OverdueLoanScheduleData>> overdueScheduleData = new HashMap<>();
|
||||||
|
for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduledInstallments) {
|
||||||
|
if (overdueScheduleData.containsKey(overdueInstallment.getLoanId())) {
|
||||||
|
overdueScheduleData.get(overdueInstallment.getLoanId()).add(overdueInstallment);
|
||||||
|
} else {
|
||||||
|
Collection<OverdueLoanScheduleData> loanData = new ArrayList<>();
|
||||||
|
loanData.add(overdueInstallment);
|
||||||
|
overdueScheduleData.put(overdueInstallment.getLoanId(), loanData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Throwable> exceptions = new ArrayList<>();
|
||||||
|
for (final Long loanId : overdueScheduleData.keySet()) {
|
||||||
|
try {
|
||||||
|
loanWritePlatformService.applyOverdueChargesForLoan(loanId, overdueScheduleData.get(loanId));
|
||||||
|
|
||||||
|
} catch (final PlatformApiDataValidationException e) {
|
||||||
|
final List<ApiParameterError> errors = e.getErrors();
|
||||||
|
for (final ApiParameterError error : errors) {
|
||||||
|
log.error("Apply Charges due for overdue loans failed for account {} with message: {}", loanId,
|
||||||
|
error.getDeveloperMessage(), e);
|
||||||
|
}
|
||||||
|
exceptions.add(e);
|
||||||
|
} catch (final AbstractPlatformDomainRuleException e) {
|
||||||
|
log.error("Apply Charges due for overdue loans failed for account {} with message: {}", loanId,
|
||||||
|
e.getDefaultUserMessage(), e);
|
||||||
|
exceptions.add(e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Apply Charges due for overdue loans failed for account {}", loanId, e);
|
||||||
|
exceptions.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exceptions.isEmpty()) {
|
||||||
|
throw new JobExecutionException(exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.applyholidaystoloans;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ApplyHolidaysToLoansConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationDomainService configurationDomainService;
|
||||||
|
@Autowired
|
||||||
|
private HolidayRepositoryWrapper holidayRepository;
|
||||||
|
@Autowired
|
||||||
|
private LoanRepositoryWrapper loanRepositoryWrapper;
|
||||||
|
@Autowired
|
||||||
|
private LoanUtilService loanUtilService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step applyHolidaysToLoansStep() {
|
||||||
|
return steps.get(JobName.APPLY_HOLIDAYS_TO_LOANS.name()).tasklet(applyHolidaysToLoansTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job applyHolidaysToLoansJob() {
|
||||||
|
return jobs.get(JobName.APPLY_HOLIDAYS_TO_LOANS.name()).start(applyHolidaysToLoansStep()).incrementer(new RunIdIncrementer())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApplyHolidaysToLoansTasklet applyHolidaysToLoansTasklet() {
|
||||||
|
return new ApplyHolidaysToLoansTasklet(configurationDomainService, holidayRepository, loanRepositoryWrapper, loanUtilService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.applyholidaystoloans;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
|
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
||||||
|
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
|
||||||
|
import org.apache.fineract.organisation.office.domain.Office;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ApplyHolidaysToLoansTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final ConfigurationDomainService configurationDomainService;
|
||||||
|
private final HolidayRepositoryWrapper holidayRepository;
|
||||||
|
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||||
|
private final LoanUtilService loanUtilService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
final boolean isHolidayEnabled = configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||||
|
|
||||||
|
if (!isHolidayEnabled) {
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Collection<Integer> loanStatuses = new ArrayList<>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
|
||||||
|
LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue()));
|
||||||
|
final List<Holiday> holidays = holidayRepository.findUnprocessed();
|
||||||
|
|
||||||
|
for (final Holiday holiday : holidays) {
|
||||||
|
final Set<Office> offices = holiday.getOffices();
|
||||||
|
final Collection<Long> officeIds = new ArrayList<>(offices.size());
|
||||||
|
for (final Office office : offices) {
|
||||||
|
officeIds.add(office.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Loan> loans = new ArrayList<>();
|
||||||
|
loans.addAll(loanRepositoryWrapper.findByClientOfficeIdsAndLoanStatus(officeIds, loanStatuses));
|
||||||
|
// FIXME: AA optimize to get all client and group loans belongs to an office id
|
||||||
|
loans.addAll(loanRepositoryWrapper.findByGroupOfficeIdsAndLoanStatus(officeIds, loanStatuses));
|
||||||
|
|
||||||
|
for (final Loan loan : loans) {
|
||||||
|
loan.applyHolidayToRepaymentScheduleDates(holiday, loanUtilService);
|
||||||
|
}
|
||||||
|
loanRepositoryWrapper.save(loans);
|
||||||
|
holiday.processed();
|
||||||
|
}
|
||||||
|
holidayRepository.save(holidays);
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.generateloanlossprovisioning;
|
||||||
|
|
||||||
|
import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesWritePlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaReadPlatformService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class GenerateLoanlossProvisioningConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private ProvisioningCriteriaReadPlatformService provisioningCriteriaReadPlatformService;
|
||||||
|
@Autowired
|
||||||
|
private ProvisioningEntriesWritePlatformService provisioningEntriesWritePlatformService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step generateLoanlossProvisioningStep() {
|
||||||
|
return steps.get(JobName.GENERATE_LOANLOSS_PROVISIONING.name()).tasklet(generateLoanlossProvisioningTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job generateLoanlossProvisioningJob() {
|
||||||
|
return jobs.get(JobName.GENERATE_LOANLOSS_PROVISIONING.name()).start(generateLoanlossProvisioningStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GenerateLoanlossProvisioningTasklet generateLoanlossProvisioningTasklet() {
|
||||||
|
return new GenerateLoanlossProvisioningTasklet(provisioningCriteriaReadPlatformService, provisioningEntriesWritePlatformService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.generateloanlossprovisioning;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Collection;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.accounting.provisioning.exception.ProvisioningEntryAlreadyCreatedException;
|
||||||
|
import org.apache.fineract.accounting.provisioning.service.ProvisioningEntriesWritePlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaData;
|
||||||
|
import org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaReadPlatformService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GenerateLoanlossProvisioningTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final ProvisioningCriteriaReadPlatformService provisioningCriteriaReadPlatformService;
|
||||||
|
private final ProvisioningEntriesWritePlatformService provisioningEntriesWritePlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
LocalDate currentDate = DateUtils.getBusinessLocalDate();
|
||||||
|
boolean addJournalEntries = true;
|
||||||
|
try {
|
||||||
|
Collection<ProvisioningCriteriaData> criteriaCollection = provisioningCriteriaReadPlatformService
|
||||||
|
.retrieveAllProvisioningCriterias();
|
||||||
|
if (criteriaCollection == null || criteriaCollection.size() == 0) {
|
||||||
|
throw new NoProvisioningCriteriaDefinitionFoundException();
|
||||||
|
}
|
||||||
|
provisioningEntriesWritePlatformService.createProvisioningEntry(currentDate, addJournalEntries);
|
||||||
|
} catch (ProvisioningEntryAlreadyCreatedException peace) {
|
||||||
|
log.error("Provisioning Entry already created", peace);
|
||||||
|
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||||
|
log.error("Problem occurred in generateLoanLossProvisioningAmount function", dve);
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-5
@@ -16,9 +16,6 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.fineract.adhocquery.service;
|
package org.apache.fineract.portfolio.loanaccount.jobs.generateloanlossprovisioning;
|
||||||
|
|
||||||
public interface AdHocScheduledJobRunnerService {
|
public class NoProvisioningCriteriaDefinitionFoundException extends RuntimeException {}
|
||||||
|
|
||||||
void generateClientSchedule();
|
|
||||||
}
|
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.recalculateinterestforloan;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.RecalculateInterestPoster;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class RecalculateInterestForLoanConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LoanReadPlatformService loanReadPlatformService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LoanWritePlatformService loanWritePlatformService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RecalculateInterestPoster recalculateInterestPoster;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OfficeReadPlatformService officeReadPlatformService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step recalculateInterestForLoanStep() {
|
||||||
|
return steps.get(JobName.RECALCULATE_INTEREST_FOR_LOAN.name()).tasklet(recalculateInterestForLoanTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job recalculateInterestForLoanJob() {
|
||||||
|
return jobs.get(JobName.RECALCULATE_INTEREST_FOR_LOAN.name()).start(recalculateInterestForLoanStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RecalculateInterestForLoanTasklet recalculateInterestForLoanTasklet() {
|
||||||
|
return new RecalculateInterestForLoanTasklet(loanReadPlatformService, loanWritePlatformService, recalculateInterestPoster,
|
||||||
|
officeReadPlatformService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+228
@@ -0,0 +1,228 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.recalculateinterestforloan;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||||
|
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.service.LoanReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.RecalculateInterestPoster;
|
||||||
|
import org.springframework.batch.core.JobParameter;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.dao.CannotAcquireLockException;
|
||||||
|
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RecalculateInterestForLoanTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final LoanReadPlatformService loanReadPlatformService;
|
||||||
|
private final LoanWritePlatformService loanWritePlatformService;
|
||||||
|
private final RecalculateInterestPoster recalculateInterestPoster;
|
||||||
|
private final OfficeReadPlatformService officeReadPlatformService;
|
||||||
|
private static final SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
Map<String, JobParameter> jobParameters = chunkContext.getStepContext().getStepExecution().getJobParameters().getParameters();
|
||||||
|
if (!jobParameters.isEmpty()) {
|
||||||
|
final String officeId = (String) jobParameters.get("officeId").getValue();
|
||||||
|
log.info("recalculateInterest: officeId={}", officeId);
|
||||||
|
Long officeIdLong = Long.valueOf(officeId);
|
||||||
|
|
||||||
|
final OfficeData office = officeReadPlatformService.retrieveOffice(officeIdLong);
|
||||||
|
if (office == null) {
|
||||||
|
throw new OfficeNotFoundException(officeIdLong);
|
||||||
|
}
|
||||||
|
final int threadPoolSize = Integer.parseInt((String) jobParameters.get("thread-pool-size").getValue());
|
||||||
|
final int batchSize = Integer.parseInt((String) jobParameters.get("batch-size").getValue());
|
||||||
|
|
||||||
|
recalculateInterest(office, threadPoolSize, batchSize);
|
||||||
|
} else {
|
||||||
|
int maxNumberOfRetries = ThreadLocalContextUtil.getTenant().getConnection().getMaxRetriesOnDeadlock();
|
||||||
|
int maxIntervalBetweenRetries = ThreadLocalContextUtil.getTenant().getConnection().getMaxIntervalBetweenRetries();
|
||||||
|
Collection<Long> loanIds = loanReadPlatformService.fetchLoansForInterestRecalculation();
|
||||||
|
int i = 0;
|
||||||
|
if (!loanIds.isEmpty()) {
|
||||||
|
List<Throwable> errors = new ArrayList<>();
|
||||||
|
for (Long loanId : loanIds) {
|
||||||
|
log.info("recalculateInterest: Loan ID = {}", loanId);
|
||||||
|
int numberOfRetries = 0;
|
||||||
|
while (numberOfRetries <= maxNumberOfRetries) {
|
||||||
|
try {
|
||||||
|
loanWritePlatformService.recalculateInterest(loanId);
|
||||||
|
numberOfRetries = maxNumberOfRetries + 1;
|
||||||
|
} catch (CannotAcquireLockException | ObjectOptimisticLockingFailureException exception) {
|
||||||
|
log.info("Recalulate interest job has been retried {} time(s)", numberOfRetries);
|
||||||
|
if (numberOfRetries >= maxNumberOfRetries) {
|
||||||
|
log.error(
|
||||||
|
"Recalulate interest job has been retried for the max allowed attempts of {} and will be rolled back",
|
||||||
|
numberOfRetries);
|
||||||
|
errors.add(exception);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int randomNum = random.nextInt(maxIntervalBetweenRetries + 1);
|
||||||
|
Thread.sleep(1000 + (randomNum * 1000L));
|
||||||
|
numberOfRetries = numberOfRetries + 1;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.error("Interest recalculation for loans retry failed due to InterruptedException", e);
|
||||||
|
errors.add(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Interest recalculation for loans failed for account {}", loanId, e);
|
||||||
|
numberOfRetries = maxNumberOfRetries + 1;
|
||||||
|
errors.add(e);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
log.info("recalculateInterest: Loans count {}", i);
|
||||||
|
}
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
throw new JobExecutionException(errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recalculateInterest(OfficeData office, int threadPoolSize, int batchSize) {
|
||||||
|
final int pageSize = batchSize * threadPoolSize;
|
||||||
|
|
||||||
|
final ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
|
||||||
|
|
||||||
|
Long maxLoanIdInList = 0L;
|
||||||
|
final String officeHierarchy = office.getHierarchy() + "%";
|
||||||
|
|
||||||
|
List<Long> loanIds = Collections.synchronizedList(
|
||||||
|
this.loanReadPlatformService.fetchLoansForInterestRecalculation(pageSize, maxLoanIdInList, officeHierarchy));
|
||||||
|
|
||||||
|
do {
|
||||||
|
int totalFilteredRecords = loanIds.size();
|
||||||
|
log.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));
|
||||||
|
|
||||||
|
executorService.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recalculateInterest(List<Long> loanIds, int threadPoolSize, int batchSize, final ExecutorService executorService) {
|
||||||
|
|
||||||
|
List<Callable<Void>> posters = new ArrayList<>();
|
||||||
|
int fromIndex = 0;
|
||||||
|
int size = loanIds.size();
|
||||||
|
double toGetCeilValue = size / threadPoolSize;
|
||||||
|
batchSize = (int) Math.ceil(toGetCeilValue);
|
||||||
|
|
||||||
|
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<Long> subList = safeSubList(loanIds, fromIndex, toIndex);
|
||||||
|
recalculateInterestPoster.setLoanIds(subList);
|
||||||
|
recalculateInterestPoster.setLoanWritePlatformService(loanWritePlatformService);
|
||||||
|
posters.add(recalculateInterestPoster);
|
||||||
|
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<Future<Void>> responses = executorService.invokeAll(posters);
|
||||||
|
checkCompletion(responses);
|
||||||
|
} catch (InterruptedException e1) {
|
||||||
|
log.error("Interrupted while recalculateInterest", e1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> List<T> safeSubList(List<T> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCompletion(List<Future<Void>> responses) {
|
||||||
|
try {
|
||||||
|
for (Future<Void> f : responses) {
|
||||||
|
f.get();
|
||||||
|
}
|
||||||
|
boolean allThreadsExecuted = false;
|
||||||
|
int noOfThreadsExecuted = 0;
|
||||||
|
for (Future<Void> future : responses) {
|
||||||
|
if (future.isDone()) {
|
||||||
|
noOfThreadsExecuted++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allThreadsExecuted = noOfThreadsExecuted == responses.size();
|
||||||
|
if (!allThreadsExecuted) {
|
||||||
|
log.error("All threads could not execute.");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e1) {
|
||||||
|
log.error("Interrupted while posting IR entries", e1);
|
||||||
|
} catch (ExecutionException e2) {
|
||||||
|
log.error("Execution exception while posting IR entries", e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.transferfeechargeforloans;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class TransferFeeChargeForLoansConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private LoanChargeReadPlatformService loanChargeReadPlatformService;
|
||||||
|
@Autowired
|
||||||
|
private AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
|
||||||
|
@Autowired
|
||||||
|
private AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step transferFeeChargeForLoansStep() {
|
||||||
|
return steps.get(JobName.TRANSFER_FEE_CHARGE_FOR_LOANS.name()).tasklet(transferFeeChargeForLoansTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job transferFeeChargeForLoansJob() {
|
||||||
|
return jobs.get(JobName.TRANSFER_FEE_CHARGE_FOR_LOANS.name()).start(transferFeeChargeForLoansStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TransferFeeChargeForLoansTasklet transferFeeChargeForLoansTasklet() {
|
||||||
|
return new TransferFeeChargeForLoansTasklet(loanChargeReadPlatformService, accountAssociationsReadPlatformService,
|
||||||
|
accountTransfersWritePlatformService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.transferfeechargeforloans;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||||
|
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||||
|
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
||||||
|
import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
|
||||||
|
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||||
|
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||||
|
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TransferFeeChargeForLoansTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
|
||||||
|
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
|
||||||
|
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
final Collection<LoanChargeData> chargeDatas = loanChargeReadPlatformService
|
||||||
|
.retrieveLoanChargesForFeePayment(ChargePaymentMode.ACCOUNT_TRANSFER.getValue(), LoanStatus.ACTIVE.getValue());
|
||||||
|
final boolean isRegularTransaction = true;
|
||||||
|
List<Throwable> errors = new ArrayList<>();
|
||||||
|
if (chargeDatas != null) {
|
||||||
|
for (final LoanChargeData chargeData : chargeDatas) {
|
||||||
|
if (chargeData.isInstallmentFee()) {
|
||||||
|
final Collection<LoanInstallmentChargeData> chargePerInstallments = loanChargeReadPlatformService
|
||||||
|
.retrieveInstallmentLoanCharges(chargeData.getId(), true);
|
||||||
|
PortfolioAccountData portfolioAccountData = null;
|
||||||
|
for (final LoanInstallmentChargeData installmentChargeData : chargePerInstallments) {
|
||||||
|
if (!installmentChargeData.getDueDate().isAfter(DateUtils.getBusinessLocalDate())) {
|
||||||
|
if (portfolioAccountData == null) {
|
||||||
|
portfolioAccountData = accountAssociationsReadPlatformService
|
||||||
|
.retriveLoanLinkedAssociation(chargeData.getLoanId());
|
||||||
|
}
|
||||||
|
final boolean isExceptionForBalanceCheck = false;
|
||||||
|
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(DateUtils.getBusinessLocalDate(),
|
||||||
|
installmentChargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
|
||||||
|
portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null,
|
||||||
|
LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(),
|
||||||
|
installmentChargeData.getInstallmentNumber(), AccountTransferType.CHARGE_PAYMENT.getValue(), null, null,
|
||||||
|
null, null, null, null, isRegularTransaction, isExceptionForBalanceCheck);
|
||||||
|
transferFeeCharge(accountTransferDTO, errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (chargeData.getDueDate() != null && !chargeData.getDueDate().isAfter(DateUtils.getBusinessLocalDate())) {
|
||||||
|
final PortfolioAccountData portfolioAccountData = accountAssociationsReadPlatformService
|
||||||
|
.retriveLoanLinkedAssociation(chargeData.getLoanId());
|
||||||
|
final boolean isExceptionForBalanceCheck = false;
|
||||||
|
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(DateUtils.getBusinessLocalDate(),
|
||||||
|
chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
|
||||||
|
portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null,
|
||||||
|
LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), null,
|
||||||
|
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, null, isRegularTransaction,
|
||||||
|
isExceptionForBalanceCheck);
|
||||||
|
transferFeeCharge(accountTransferDTO, errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
throw new JobExecutionException(errors);
|
||||||
|
}
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transferFeeCharge(final AccountTransferDTO accountTransferDTO, List<Throwable> errors) {
|
||||||
|
try {
|
||||||
|
accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
log.error("Exception while paying charge {} for loan id {}", accountTransferDTO.getChargeId(),
|
||||||
|
accountTransferDTO.getToAccountId(), e);
|
||||||
|
errors.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.updateloanarrearsageing;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class UpdateLoanArrearsAgeingConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
@Autowired
|
||||||
|
private DatabaseSpecificSQLGenerator sqlGenerator;
|
||||||
|
@Autowired
|
||||||
|
private LoanArrearsAgingService loanArrearsAgingService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step updateLoanArrearsAgeingStep() {
|
||||||
|
return steps.get(JobName.UPDATE_LOAN_ARREARS_AGEING.name()).tasklet(updateLoanArrearsAgeingTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job updateLoanArrearsAgeingJob() {
|
||||||
|
return jobs.get(JobName.UPDATE_LOAN_ARREARS_AGEING.name()).start(updateLoanArrearsAgeingStep()).incrementer(new RunIdIncrementer())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UpdateLoanArrearsAgeingTasklet updateLoanArrearsAgeingTasklet() {
|
||||||
|
return new UpdateLoanArrearsAgeingTasklet(jdbcTemplate, sqlGenerator, loanArrearsAgingService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* 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.jobs.updateloanarrearsageing;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpdateLoanArrearsAgeingTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||||
|
private final LoanArrearsAgingService loanArrearsAgingService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
jdbcTemplate.execute("truncate table m_loan_arrears_aging");
|
||||||
|
|
||||||
|
final StringBuilder updateSqlBuilder = new StringBuilder(900);
|
||||||
|
final String principalOverdueCalculationSql = "SUM(COALESCE(mr.principal_amount, 0) - coalesce(mr.principal_completed_derived, 0) - coalesce(mr.principal_writtenoff_derived, 0))";
|
||||||
|
final String interestOverdueCalculationSql = "SUM(COALESCE(mr.interest_amount, 0) - coalesce(mr.interest_writtenoff_derived, 0) - coalesce(mr.interest_waived_derived, 0) - "
|
||||||
|
+ "coalesce(mr.interest_completed_derived, 0))";
|
||||||
|
final String feeChargesOverdueCalculationSql = "SUM(COALESCE(mr.fee_charges_amount, 0) - coalesce(mr.fee_charges_writtenoff_derived, 0) - "
|
||||||
|
+ "coalesce(mr.fee_charges_waived_derived, 0) - coalesce(mr.fee_charges_completed_derived, 0))";
|
||||||
|
final String penaltyChargesOverdueCalculationSql = "SUM(COALESCE(mr.penalty_charges_amount, 0) - coalesce(mr.penalty_charges_writtenoff_derived, 0) - "
|
||||||
|
+ "coalesce(mr.penalty_charges_waived_derived, 0) - coalesce(mr.penalty_charges_completed_derived, 0))";
|
||||||
|
|
||||||
|
updateSqlBuilder.append(
|
||||||
|
"INSERT INTO m_loan_arrears_aging(loan_id,principal_overdue_derived,interest_overdue_derived,fee_charges_overdue_derived,penalty_charges_overdue_derived,total_overdue_derived,overdue_since_date_derived)");
|
||||||
|
updateSqlBuilder.append("select ml.id as loanId,");
|
||||||
|
updateSqlBuilder.append(principalOverdueCalculationSql + " as principal_overdue_derived,");
|
||||||
|
updateSqlBuilder.append(interestOverdueCalculationSql + " as interest_overdue_derived,");
|
||||||
|
updateSqlBuilder.append(feeChargesOverdueCalculationSql + " as fee_charges_overdue_derived,");
|
||||||
|
updateSqlBuilder.append(penaltyChargesOverdueCalculationSql + " as penalty_charges_overdue_derived,");
|
||||||
|
updateSqlBuilder.append(principalOverdueCalculationSql + "+" + interestOverdueCalculationSql + "+");
|
||||||
|
updateSqlBuilder.append(feeChargesOverdueCalculationSql + "+" + penaltyChargesOverdueCalculationSql + " as total_overdue_derived,");
|
||||||
|
updateSqlBuilder.append("MIN(mr.duedate) as overdue_since_date_derived ");
|
||||||
|
updateSqlBuilder.append(" FROM m_loan ml ");
|
||||||
|
updateSqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
|
||||||
|
updateSqlBuilder.append(" left join m_product_loan_recalculation_details prd on prd.product_id = ml.product_id ");
|
||||||
|
updateSqlBuilder.append(" WHERE ml.loan_status_id = 300 "); // active
|
||||||
|
updateSqlBuilder.append(" and mr.completed_derived is false ");
|
||||||
|
updateSqlBuilder.append(" and mr.duedate < ")
|
||||||
|
.append(sqlGenerator.subDate(sqlGenerator.currentBusinessDate(), "COALESCE(ml.grace_on_arrears_ageing, 0)", "day"))
|
||||||
|
.append(" ");
|
||||||
|
updateSqlBuilder.append(" and (prd.arrears_based_on_original_schedule = false or prd.arrears_based_on_original_schedule is null) ");
|
||||||
|
updateSqlBuilder.append(" GROUP BY ml.id");
|
||||||
|
|
||||||
|
List<String> insertStatements = updateLoanArrearsAgeingDetailsWithOriginalSchedule();
|
||||||
|
insertStatements.add(0, updateSqlBuilder.toString());
|
||||||
|
final int[] results = this.jdbcTemplate.batchUpdate(insertStatements.toArray(new String[0]));
|
||||||
|
int result = 0;
|
||||||
|
for (int i : results) {
|
||||||
|
result += i;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("{}: Records affected by updateLoanArrearsAgeingDetails: {}", ThreadLocalContextUtil.getTenant().getName(), result);
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> updateLoanArrearsAgeingDetailsWithOriginalSchedule() {
|
||||||
|
List<String> insertStatement = new ArrayList<>();
|
||||||
|
|
||||||
|
final StringBuilder loanIdentifier = new StringBuilder();
|
||||||
|
loanIdentifier.append("select ml.id as loanId FROM m_loan ml ");
|
||||||
|
loanIdentifier.append("INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
|
||||||
|
loanIdentifier.append(
|
||||||
|
"inner join m_product_loan_recalculation_details prd on prd.product_id = ml.product_id and prd.arrears_based_on_original_schedule = true ");
|
||||||
|
loanIdentifier.append("WHERE ml.loan_status_id = 300 and mr.completed_derived is false and mr.duedate < ")
|
||||||
|
.append(sqlGenerator.subDate(sqlGenerator.currentBusinessDate(), "COALESCE(ml.grace_on_arrears_ageing, 0)", "day"))
|
||||||
|
.append(" group by ml.id");
|
||||||
|
List<Long> loanIds = this.jdbcTemplate.queryForList(loanIdentifier.toString(), Long.class);
|
||||||
|
if (!loanIds.isEmpty()) {
|
||||||
|
String loanIdsAsString = loanIds.toString();
|
||||||
|
loanIdsAsString = loanIdsAsString.substring(1, loanIdsAsString.length() - 1);
|
||||||
|
Map<Long, List<LoanSchedulePeriodData>> scheduleDate = loanArrearsAgingService.getScheduleDate(loanIdsAsString);
|
||||||
|
|
||||||
|
List<Map<String, Object>> loanSummary = getLoanSummary(loanIdsAsString);
|
||||||
|
loanArrearsAgingService.updateScheduleWithPaidDetail(scheduleDate, loanSummary);
|
||||||
|
loanArrearsAgingService.createInsertStatements(insertStatement, scheduleDate, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return insertStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> getLoanSummary(final String loanIdsAsString) {
|
||||||
|
final StringBuilder transactionsSql = new StringBuilder();
|
||||||
|
transactionsSql.append("select ml.id as loanId, ");
|
||||||
|
transactionsSql
|
||||||
|
.append("ml.principal_repaid_derived as principalAmtPaid, ml.principal_writtenoff_derived as principalAmtWrittenoff, ");
|
||||||
|
transactionsSql.append(" ml.interest_repaid_derived as interestAmtPaid, ml.interest_waived_derived as interestAmtWaived, ");
|
||||||
|
transactionsSql.append("ml.fee_charges_repaid_derived as feeAmtPaid, ml.fee_charges_waived_derived as feeAmtWaived, ");
|
||||||
|
transactionsSql
|
||||||
|
.append("ml.penalty_charges_repaid_derived as penaltyAmtPaid, ml.penalty_charges_waived_derived as penaltyAmtWaived ");
|
||||||
|
transactionsSql.append("from m_loan ml ");
|
||||||
|
transactionsSql.append("where ml.id IN (").append(loanIdsAsString).append(") order by ml.id");
|
||||||
|
|
||||||
|
List<Map<String, Object>> loanSummary = this.jdbcTemplate.queryForList(transactionsSql.toString());
|
||||||
|
return loanSummary;
|
||||||
|
}
|
||||||
|
}
|
||||||
-7
@@ -21,7 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.service;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||||
|
|
||||||
public interface LoanAccrualPlatformService {
|
public interface LoanAccrualPlatformService {
|
||||||
@@ -30,10 +29,4 @@ public interface LoanAccrualPlatformService {
|
|||||||
|
|
||||||
void addPeriodicAccruals(LocalDate tilldate, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws MultiException;
|
void addPeriodicAccruals(LocalDate tilldate, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws MultiException;
|
||||||
|
|
||||||
void addAccrualAccounting() throws JobExecutionException;
|
|
||||||
|
|
||||||
void addPeriodicAccruals() throws JobExecutionException;
|
|
||||||
|
|
||||||
void addPeriodicAccrualsForLoansWithIncomePostedAsTransactions() throws JobExecutionException;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-76
@@ -24,71 +24,20 @@ import java.util.Collection;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
|
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformService {
|
public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformService {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(LoanAccrualPlatformServiceImpl.class);
|
|
||||||
|
|
||||||
private final LoanReadPlatformService loanReadPlatformService;
|
private final LoanReadPlatformService loanReadPlatformService;
|
||||||
private final LoanAccrualWritePlatformService loanAccrualWritePlatformService;
|
private final LoanAccrualWritePlatformService loanAccrualWritePlatformService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public LoanAccrualPlatformServiceImpl(final LoanReadPlatformService loanReadPlatformService,
|
|
||||||
final LoanAccrualWritePlatformService loanAccrualWritePlatformService) {
|
|
||||||
this.loanReadPlatformService = loanReadPlatformService;
|
|
||||||
this.loanAccrualWritePlatformService = loanAccrualWritePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.ADD_ACCRUAL_ENTRIES)
|
|
||||||
public void addAccrualAccounting() throws JobExecutionException {
|
|
||||||
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = this.loanReadPlatformService.retriveScheduleAccrualData();
|
|
||||||
Map<Long, Collection<LoanScheduleAccrualData>> loanDataMap = new HashMap<>();
|
|
||||||
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDatas) {
|
|
||||||
if (loanDataMap.containsKey(accrualData.getLoanId())) {
|
|
||||||
loanDataMap.get(accrualData.getLoanId()).add(accrualData);
|
|
||||||
} else {
|
|
||||||
Collection<LoanScheduleAccrualData> accrualDatas = new ArrayList<>();
|
|
||||||
accrualDatas.add(accrualData);
|
|
||||||
loanDataMap.put(accrualData.getLoanId(), accrualDatas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Throwable> errors = new ArrayList<>();
|
|
||||||
for (Map.Entry<Long, Collection<LoanScheduleAccrualData>> mapEntry : loanDataMap.entrySet()) {
|
|
||||||
try {
|
|
||||||
this.loanAccrualWritePlatformService.addAccrualAccounting(mapEntry.getKey(), mapEntry.getValue());
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Failed to add accural transaction for loan {}", mapEntry.getKey(), e);
|
|
||||||
errors.add(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
throw new JobExecutionException(errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.ADD_PERIODIC_ACCRUAL_ENTRIES)
|
|
||||||
public void addPeriodicAccruals() throws JobExecutionException {
|
|
||||||
try {
|
|
||||||
addPeriodicAccruals(DateUtils.getBusinessLocalDate());
|
|
||||||
} catch (MultiException e) {
|
|
||||||
throw new JobExecutionException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addPeriodicAccruals(final LocalDate tilldate) throws JobExecutionException {
|
public void addPeriodicAccruals(final LocalDate tilldate) throws JobExecutionException {
|
||||||
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = this.loanReadPlatformService.retrivePeriodicAccrualData(tilldate);
|
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = this.loanReadPlatformService.retrivePeriodicAccrualData(tilldate);
|
||||||
@@ -114,7 +63,7 @@ public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformServic
|
|||||||
try {
|
try {
|
||||||
this.loanAccrualWritePlatformService.addPeriodicAccruals(tilldate, mapEntry.getKey(), mapEntry.getValue());
|
this.loanAccrualWritePlatformService.addPeriodicAccruals(tilldate, mapEntry.getKey(), mapEntry.getValue());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to add accural transaction for loan {}", mapEntry.getKey(), e);
|
log.error("Failed to add accural transaction for loan {}", mapEntry.getKey(), e);
|
||||||
errors.add(e);
|
errors.add(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,24 +71,4 @@ public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformServic
|
|||||||
throw new JobExecutionException(errors);
|
throw new JobExecutionException(errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS)
|
|
||||||
public void addPeriodicAccrualsForLoansWithIncomePostedAsTransactions() throws JobExecutionException {
|
|
||||||
Collection<Long> loanIds = this.loanReadPlatformService.retrieveLoanIdsWithPendingIncomePostingTransactions();
|
|
||||||
if (loanIds != null && loanIds.size() > 0) {
|
|
||||||
List<Throwable> errors = new ArrayList<>();
|
|
||||||
for (Long loanId : loanIds) {
|
|
||||||
try {
|
|
||||||
this.loanAccrualWritePlatformService.addIncomeAndAccrualTransactions(loanId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Failed to add income and accrual transaction for loan {}", loanId, e);
|
|
||||||
errors.add(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
throw new JobExecutionException(errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-19
@@ -28,6 +28,7 @@ import java.util.Iterator;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
@@ -47,13 +48,13 @@ import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException
|
|||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||||
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
|
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
|
||||||
import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
|
import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlatformService {
|
public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlatformService {
|
||||||
|
|
||||||
private final LoanReadPlatformService loanReadPlatformService;
|
private final LoanReadPlatformService loanReadPlatformService;
|
||||||
@@ -65,22 +66,6 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
|
|||||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||||
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
|
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public LoanAccrualWritePlatformServiceImpl(final JdbcTemplate jdbcTemplate, final LoanReadPlatformService loanReadPlatformService,
|
|
||||||
final JournalEntryWritePlatformService journalEntryWritePlatformService,
|
|
||||||
final LoanChargeReadPlatformService loanChargeReadPlatformService, final AppUserRepositoryWrapper userRepository,
|
|
||||||
final LoanRepositoryWrapper loanRepositoryWrapper, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
|
|
||||||
DatabaseSpecificSQLGenerator sqlGenerator) {
|
|
||||||
this.loanReadPlatformService = loanReadPlatformService;
|
|
||||||
this.sqlGenerator = sqlGenerator;
|
|
||||||
this.jdbcTemplate = jdbcTemplate;
|
|
||||||
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
|
|
||||||
this.loanChargeReadPlatformService = loanChargeReadPlatformService;
|
|
||||||
this.userRepository = userRepository;
|
|
||||||
this.loanRepositoryWrapper = loanRepositoryWrapper;
|
|
||||||
this.applicationCurrencyRepository = applicationCurrencyRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void addAccrualAccounting(final Long loanId, final Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas)
|
public void addAccrualAccounting(final Long loanId, final Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas)
|
||||||
@@ -503,10 +488,10 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
|
|||||||
private void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
|
private void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
|
||||||
final List<Long> existingReversedTransactionIds) {
|
final List<Long> existingReversedTransactionIds) {
|
||||||
final MonetaryCurrency currency = loan.getCurrency();
|
final MonetaryCurrency currency = loan.getCurrency();
|
||||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
final ApplicationCurrency applicationCurrency = applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||||
boolean isAccountTransfer = false;
|
boolean isAccountTransfer = false;
|
||||||
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
|
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
|
||||||
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||||
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-2
@@ -18,14 +18,21 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||||
|
|
||||||
public interface LoanArrearsAgingService {
|
public interface LoanArrearsAgingService {
|
||||||
|
|
||||||
void updateLoanArrearsAgeingDetails();
|
|
||||||
|
|
||||||
void updateLoanArrearsAgeingDetailsWithOriginalSchedule(Loan loan);
|
void updateLoanArrearsAgeingDetailsWithOriginalSchedule(Loan loan);
|
||||||
|
|
||||||
|
Map<Long, List<LoanSchedulePeriodData>> getScheduleDate(String loanId);
|
||||||
|
|
||||||
void updateLoanArrearsAgeingDetails(Loan loan);
|
void updateLoanArrearsAgeingDetails(Loan loan);
|
||||||
|
|
||||||
|
void createInsertStatements(List<String> insertStatement, Map<Long, List<LoanSchedulePeriodData>> scheduleDate,
|
||||||
|
boolean isInsertStatement);
|
||||||
|
|
||||||
|
void updateScheduleWithPaidDetail(Map<Long, List<LoanSchedulePeriodData>> scheduleDate, List<Map<String, Object>> loanSummary);
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-127
@@ -32,10 +32,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
|
||||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
|
|
||||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
|
||||||
import org.apache.fineract.portfolio.businessevent.BusinessEventListener;
|
import org.apache.fineract.portfolio.businessevent.BusinessEventListener;
|
||||||
import org.apache.fineract.portfolio.businessevent.domain.loan.LoanAdjustTransactionBusinessEvent;
|
import org.apache.fineract.portfolio.businessevent.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||||
import org.apache.fineract.portfolio.businessevent.domain.loan.LoanApplyOverdueChargeBusinessEvent;
|
import org.apache.fineract.portfolio.businessevent.domain.loan.LoanApplyOverdueChargeBusinessEvent;
|
||||||
@@ -59,7 +56,6 @@ import org.springframework.dao.DataAccessException;
|
|||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -92,54 +88,6 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
|||||||
new LoanForeClosureEventListener());
|
new LoanForeClosureEventListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.UPDATE_LOAN_ARREARS_AGEING)
|
|
||||||
public void updateLoanArrearsAgeingDetails() {
|
|
||||||
|
|
||||||
this.jdbcTemplate.execute("truncate table m_loan_arrears_aging");
|
|
||||||
|
|
||||||
final StringBuilder updateSqlBuilder = new StringBuilder(900);
|
|
||||||
final String principalOverdueCalculationSql = "SUM(COALESCE(mr.principal_amount, 0) - coalesce(mr.principal_completed_derived, 0) - coalesce(mr.principal_writtenoff_derived, 0))";
|
|
||||||
final String interestOverdueCalculationSql = "SUM(COALESCE(mr.interest_amount, 0) - coalesce(mr.interest_writtenoff_derived, 0) - coalesce(mr.interest_waived_derived, 0) - "
|
|
||||||
+ "coalesce(mr.interest_completed_derived, 0))";
|
|
||||||
final String feeChargesOverdueCalculationSql = "SUM(COALESCE(mr.fee_charges_amount, 0) - coalesce(mr.fee_charges_writtenoff_derived, 0) - "
|
|
||||||
+ "coalesce(mr.fee_charges_waived_derived, 0) - coalesce(mr.fee_charges_completed_derived, 0))";
|
|
||||||
final String penaltyChargesOverdueCalculationSql = "SUM(COALESCE(mr.penalty_charges_amount, 0) - coalesce(mr.penalty_charges_writtenoff_derived, 0) - "
|
|
||||||
+ "coalesce(mr.penalty_charges_waived_derived, 0) - coalesce(mr.penalty_charges_completed_derived, 0))";
|
|
||||||
|
|
||||||
updateSqlBuilder.append(
|
|
||||||
"INSERT INTO m_loan_arrears_aging(loan_id,principal_overdue_derived,interest_overdue_derived,fee_charges_overdue_derived,penalty_charges_overdue_derived,total_overdue_derived,overdue_since_date_derived)");
|
|
||||||
updateSqlBuilder.append("select ml.id as loanId,");
|
|
||||||
updateSqlBuilder.append(principalOverdueCalculationSql + " as principal_overdue_derived,");
|
|
||||||
updateSqlBuilder.append(interestOverdueCalculationSql + " as interest_overdue_derived,");
|
|
||||||
updateSqlBuilder.append(feeChargesOverdueCalculationSql + " as fee_charges_overdue_derived,");
|
|
||||||
updateSqlBuilder.append(penaltyChargesOverdueCalculationSql + " as penalty_charges_overdue_derived,");
|
|
||||||
updateSqlBuilder.append(principalOverdueCalculationSql + "+" + interestOverdueCalculationSql + "+");
|
|
||||||
updateSqlBuilder.append(feeChargesOverdueCalculationSql + "+" + penaltyChargesOverdueCalculationSql + " as total_overdue_derived,");
|
|
||||||
updateSqlBuilder.append("MIN(mr.duedate) as overdue_since_date_derived ");
|
|
||||||
updateSqlBuilder.append(" FROM m_loan ml ");
|
|
||||||
updateSqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
|
|
||||||
updateSqlBuilder.append(" left join m_product_loan_recalculation_details prd on prd.product_id = ml.product_id ");
|
|
||||||
updateSqlBuilder.append(" WHERE ml.loan_status_id = 300 "); // active
|
|
||||||
updateSqlBuilder.append(" and mr.completed_derived is false ");
|
|
||||||
updateSqlBuilder.append(" and mr.duedate < ")
|
|
||||||
.append(sqlGenerator.subDate(sqlGenerator.currentBusinessDate(), "COALESCE(ml.grace_on_arrears_ageing, 0)", "day"))
|
|
||||||
.append(" ");
|
|
||||||
updateSqlBuilder.append(" and (prd.arrears_based_on_original_schedule = false or prd.arrears_based_on_original_schedule is null) ");
|
|
||||||
updateSqlBuilder.append(" GROUP BY ml.id");
|
|
||||||
|
|
||||||
List<String> insertStatements = updateLoanArrearsAgeingDetailsWithOriginalSchedule();
|
|
||||||
insertStatements.add(0, updateSqlBuilder.toString());
|
|
||||||
final int[] results = this.jdbcTemplate.batchUpdate(insertStatements.toArray(new String[0]));
|
|
||||||
int result = 0;
|
|
||||||
for (int i : results) {
|
|
||||||
result += i;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("{}: Records affected by updateLoanArrearsAgeingDetails: {}", ThreadLocalContextUtil.getTenant().getName(), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateLoanArrearsAgeingDetailsWithOriginalSchedule(final Loan loan) {
|
public void updateLoanArrearsAgeingDetailsWithOriginalSchedule(final Loan loan) {
|
||||||
int count = this.jdbcTemplate.queryForObject("select count(mla.loan_id) from m_loan_arrears_aging mla where mla.loan_id =?",
|
int count = this.jdbcTemplate.queryForObject("select count(mla.loan_id) from m_loan_arrears_aging mla where mla.loan_id =?",
|
||||||
@@ -150,7 +98,7 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
|||||||
originalScheduleExtractor);
|
originalScheduleExtractor);
|
||||||
if (scheduleDate.size() > 0) {
|
if (scheduleDate.size() > 0) {
|
||||||
List<Map<String, Object>> transactions = getLoanSummary(loan.getId(), loan.getLoanSummary());
|
List<Map<String, Object>> transactions = getLoanSummary(loan.getId(), loan.getLoanSummary());
|
||||||
updateSchheduleWithPaidDetail(scheduleDate, transactions);
|
updateScheduleWithPaidDetail(scheduleDate, transactions);
|
||||||
createInsertStatements(updateStatement, scheduleDate, count == 0);
|
createInsertStatements(updateStatement, scheduleDate, count == 0);
|
||||||
if (updateStatement.size() == 1) {
|
if (updateStatement.size() == 1) {
|
||||||
this.jdbcTemplate.update(updateStatement.get(0));
|
this.jdbcTemplate.update(updateStatement.get(0));
|
||||||
@@ -161,6 +109,12 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Long, List<LoanSchedulePeriodData>> getScheduleDate(String loanId) {
|
||||||
|
OriginalScheduleExtractor originalScheduleExtractor = new OriginalScheduleExtractor(loanId, sqlGenerator);
|
||||||
|
return this.jdbcTemplate.query(originalScheduleExtractor.schema, originalScheduleExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateLoanArrearsAgeingDetails(final Loan loan) {
|
public void updateLoanArrearsAgeingDetails(final Loan loan) {
|
||||||
int count = this.jdbcTemplate.queryForObject("select count(mla.loan_id) from m_loan_arrears_aging mla where mla.loan_id =?",
|
int count = this.jdbcTemplate.queryForObject("select count(mla.loan_id) from m_loan_arrears_aging mla where mla.loan_id =?",
|
||||||
@@ -208,50 +162,6 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
|||||||
return updateSql;
|
return updateSql;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> updateLoanArrearsAgeingDetailsWithOriginalSchedule() {
|
|
||||||
List<String> insertStatement = new ArrayList<>();
|
|
||||||
|
|
||||||
final StringBuilder loanIdentifier = new StringBuilder();
|
|
||||||
loanIdentifier.append("select ml.id as loanId FROM m_loan ml ");
|
|
||||||
loanIdentifier.append("INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
|
|
||||||
loanIdentifier.append(
|
|
||||||
"inner join m_product_loan_recalculation_details prd on prd.product_id = ml.product_id and prd.arrears_based_on_original_schedule = true ");
|
|
||||||
loanIdentifier.append("WHERE ml.loan_status_id = 300 and mr.completed_derived is false and mr.duedate < ")
|
|
||||||
.append(sqlGenerator.subDate(sqlGenerator.currentBusinessDate(), "COALESCE(ml.grace_on_arrears_ageing, 0)", "day"))
|
|
||||||
.append(" group by ml.id");
|
|
||||||
List<Long> loanIds = this.jdbcTemplate.queryForList(loanIdentifier.toString(), Long.class);
|
|
||||||
if (!loanIds.isEmpty()) {
|
|
||||||
String loanIdsAsString = loanIds.toString();
|
|
||||||
loanIdsAsString = loanIdsAsString.substring(1, loanIdsAsString.length() - 1);
|
|
||||||
OriginalScheduleExtractor originalScheduleExtractor = new OriginalScheduleExtractor(loanIdsAsString, sqlGenerator);
|
|
||||||
Map<Long, List<LoanSchedulePeriodData>> scheduleDate = this.jdbcTemplate.query(originalScheduleExtractor.schema,
|
|
||||||
originalScheduleExtractor);
|
|
||||||
|
|
||||||
List<Map<String, Object>> loanSummary = getLoanSummary(loanIdsAsString);
|
|
||||||
updateSchheduleWithPaidDetail(scheduleDate, loanSummary);
|
|
||||||
createInsertStatements(insertStatement, scheduleDate, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return insertStatement;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Map<String, Object>> getLoanSummary(final String loanIdsAsString) {
|
|
||||||
final StringBuilder transactionsSql = new StringBuilder();
|
|
||||||
transactionsSql.append("select ml.id as loanId, ");
|
|
||||||
transactionsSql
|
|
||||||
.append("ml.principal_repaid_derived as principalAmtPaid, ml.principal_writtenoff_derived as principalAmtWrittenoff, ");
|
|
||||||
transactionsSql.append(" ml.interest_repaid_derived as interestAmtPaid, ml.interest_waived_derived as interestAmtWaived, ");
|
|
||||||
transactionsSql.append("ml.fee_charges_repaid_derived as feeAmtPaid, ml.fee_charges_waived_derived as feeAmtWaived, ");
|
|
||||||
transactionsSql
|
|
||||||
.append("ml.penalty_charges_repaid_derived as penaltyAmtPaid, ml.penalty_charges_waived_derived as penaltyAmtWaived ");
|
|
||||||
transactionsSql.append("from m_loan ml ");
|
|
||||||
transactionsSql.append("where ml.id IN (").append(loanIdsAsString).append(") order by ml.id");
|
|
||||||
|
|
||||||
List<Map<String, Object>> loanSummary = this.jdbcTemplate.queryForList(transactionsSql.toString());
|
|
||||||
return loanSummary;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Map<String, Object>> getLoanSummary(final Long loanId, final LoanSummary loanSummary) {
|
private List<Map<String, Object>> getLoanSummary(final Long loanId, final LoanSummary loanSummary) {
|
||||||
List<Map<String, Object>> transactionDetail = new ArrayList<>();
|
List<Map<String, Object>> transactionDetail = new ArrayList<>();
|
||||||
Map<String, Object> transactionMap = new HashMap<>();
|
Map<String, Object> transactionMap = new HashMap<>();
|
||||||
@@ -270,7 +180,8 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createInsertStatements(List<String> insertStatement, Map<Long, List<LoanSchedulePeriodData>> scheduleDate,
|
@Override
|
||||||
|
public void createInsertStatements(List<String> insertStatement, Map<Long, List<LoanSchedulePeriodData>> scheduleDate,
|
||||||
boolean isInsertStatement) {
|
boolean isInsertStatement) {
|
||||||
for (Map.Entry<Long, List<LoanSchedulePeriodData>> entry : scheduleDate.entrySet()) {
|
for (Map.Entry<Long, List<LoanSchedulePeriodData>> entry : scheduleDate.entrySet()) {
|
||||||
final Long loanId = entry.getKey();
|
final Long loanId = entry.getKey();
|
||||||
@@ -341,25 +252,10 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
|||||||
return insertStatementBuilder.toString();
|
return insertStatementBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSchheduleWithPaidDetail(Map<Long, List<LoanSchedulePeriodData>> scheduleDate,
|
@Override
|
||||||
List<Map<String, Object>> loanSummary) {
|
public void updateScheduleWithPaidDetail(Map<Long, List<LoanSchedulePeriodData>> scheduleDate, List<Map<String, Object>> loanSummary) {
|
||||||
for (Map<String, Object> transactionMap : loanSummary) {
|
for (Map<String, Object> transactionMap : loanSummary) {
|
||||||
String longValue = transactionMap.get("loanId").toString(); // From
|
String longValue = transactionMap.get("loanId").toString();
|
||||||
// JDBC
|
|
||||||
// Template
|
|
||||||
// API,
|
|
||||||
// we
|
|
||||||
// are
|
|
||||||
// getting
|
|
||||||
// BigInteger
|
|
||||||
// but
|
|
||||||
// in
|
|
||||||
// other
|
|
||||||
// call,
|
|
||||||
// we
|
|
||||||
// are
|
|
||||||
// getting
|
|
||||||
// Long
|
|
||||||
Long loanId = Long.parseLong(longValue);
|
Long loanId = Long.parseLong(longValue);
|
||||||
BigDecimal principalAmtPaid = (BigDecimal) transactionMap.get("principalAmtPaid");
|
BigDecimal principalAmtPaid = (BigDecimal) transactionMap.get("principalAmtPaid");
|
||||||
BigDecimal principalAmtWrittenoff = (BigDecimal) transactionMap.get("principalAmtWrittenoff");
|
BigDecimal principalAmtWrittenoff = (BigDecimal) transactionMap.get("principalAmtWrittenoff");
|
||||||
@@ -375,15 +271,15 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
|||||||
BigDecimal feeAmt = feeAmtPaid.add(feeAmtWaived);
|
BigDecimal feeAmt = feeAmtPaid.add(feeAmtWaived);
|
||||||
BigDecimal penaltyAmt = penaltyAmtPaid.add(penaltyAmtWaived);
|
BigDecimal penaltyAmt = penaltyAmtPaid.add(penaltyAmtWaived);
|
||||||
|
|
||||||
List<LoanSchedulePeriodData> loanSchedulePeriodDatas = scheduleDate.get(loanId);
|
List<LoanSchedulePeriodData> loanSchedulePeriodDataList = scheduleDate.get(loanId);
|
||||||
if (loanSchedulePeriodDatas != null) {
|
if (loanSchedulePeriodDataList != null) {
|
||||||
List<LoanSchedulePeriodData> updatedPeriodData = new ArrayList<>(loanSchedulePeriodDatas.size());
|
List<LoanSchedulePeriodData> updatedPeriodData = new ArrayList<>(loanSchedulePeriodDataList.size());
|
||||||
for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDatas) {
|
for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDataList) {
|
||||||
BigDecimal principalPaid = null;
|
BigDecimal principalPaid;
|
||||||
BigDecimal interestPaid = null;
|
BigDecimal interestPaid;
|
||||||
BigDecimal feeChargesPaid = null;
|
BigDecimal feeChargesPaid;
|
||||||
BigDecimal penaltyChargesPaid = null;
|
BigDecimal penaltyChargesPaid;
|
||||||
Boolean isComplete = true;
|
boolean isComplete = true;
|
||||||
if (loanSchedulePeriodData.principalDue().compareTo(principalAmt) > 0) {
|
if (loanSchedulePeriodData.principalDue().compareTo(principalAmt) > 0) {
|
||||||
principalPaid = principalAmt;
|
principalPaid = principalAmt;
|
||||||
principalAmt = BigDecimal.ZERO;
|
principalAmt = BigDecimal.ZERO;
|
||||||
@@ -422,8 +318,8 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
|||||||
principalPaid, interestPaid, feeChargesPaid, penaltyChargesPaid);
|
principalPaid, interestPaid, feeChargesPaid, penaltyChargesPaid);
|
||||||
updatedPeriodData.add(periodData);
|
updatedPeriodData.add(periodData);
|
||||||
}
|
}
|
||||||
loanSchedulePeriodDatas.clear();
|
loanSchedulePeriodDataList.clear();
|
||||||
loanSchedulePeriodDatas.addAll(updatedPeriodData);
|
loanSchedulePeriodDataList.addAll(updatedPeriodData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-296
@@ -1,296 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.fineract.cob.loan.ApplyChargeToOverdueLoansBusinessStep;
|
|
||||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
|
||||||
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.domain.LoanRepository;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
|
|
||||||
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
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
|
||||||
public class LoanSchedularServiceImpl implements LoanSchedularService {
|
|
||||||
|
|
||||||
private static final SecureRandom RANDOM = new SecureRandom();
|
|
||||||
|
|
||||||
private final ConfigurationDomainService configurationDomainService;
|
|
||||||
private final LoanReadPlatformService loanReadPlatformService;
|
|
||||||
private final LoanWritePlatformService loanWritePlatformService;
|
|
||||||
private final OfficeReadPlatformService officeReadPlatformService;
|
|
||||||
private final ApplicationContext applicationContext;
|
|
||||||
private final ApplyChargeToOverdueLoansBusinessStep applyChargeToOverdueLoansBusinessStep;
|
|
||||||
private final LoanRepository loanRepository;
|
|
||||||
|
|
||||||
@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<OverdueLoanScheduleData> overdueLoanScheduledInstallments = this.loanReadPlatformService
|
|
||||||
.retrieveAllLoansWithOverdueInstallments(penaltyWaitPeriodValue, backdatePenalties);
|
|
||||||
|
|
||||||
Set<Long> loanIds = overdueLoanScheduledInstallments.stream().map(OverdueLoanScheduleData::getLoanId).collect(Collectors.toSet());
|
|
||||||
|
|
||||||
if (!loanIds.isEmpty()) {
|
|
||||||
List<Throwable> exceptions = new ArrayList<>();
|
|
||||||
for (final Long loanId : loanIds) {
|
|
||||||
try {
|
|
||||||
applyChargeToOverdueLoansBusinessStep.execute(loanRepository.getReferenceById(loanId));
|
|
||||||
} catch (final PlatformApiDataValidationException e) {
|
|
||||||
final List<ApiParameterError> errors = e.getErrors();
|
|
||||||
for (final ApiParameterError error : errors) {
|
|
||||||
log.error("Apply Charges due for overdue loans failed for account {} with message: {}", loanId,
|
|
||||||
error.getDeveloperMessage(), e);
|
|
||||||
}
|
|
||||||
exceptions.add(e);
|
|
||||||
} catch (final AbstractPlatformDomainRuleException e) {
|
|
||||||
log.error("Apply Charges due for overdue loans failed for account {} with message: {}", loanId,
|
|
||||||
e.getDefaultUserMessage(), e);
|
|
||||||
exceptions.add(e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Apply Charges due for overdue loans failed for account {}", loanId, e);
|
|
||||||
exceptions.add(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!exceptions.isEmpty()) {
|
|
||||||
throw new JobExecutionException(exceptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.RECALCULATE_INTEREST_FOR_LOAN)
|
|
||||||
@SuppressFBWarnings(value = {
|
|
||||||
"DMI_RANDOM_USED_ONLY_ONCE" }, justification = "False positive for random object created and used only once")
|
|
||||||
public void recalculateInterest() throws JobExecutionException {
|
|
||||||
Integer maxNumberOfRetries = ThreadLocalContextUtil.getTenant().getConnection().getMaxRetriesOnDeadlock();
|
|
||||||
Integer maxIntervalBetweenRetries = ThreadLocalContextUtil.getTenant().getConnection().getMaxIntervalBetweenRetries();
|
|
||||||
Collection<Long> loanIds = this.loanReadPlatformService.fetchLoansForInterestRecalculation();
|
|
||||||
int i = 0;
|
|
||||||
if (!loanIds.isEmpty()) {
|
|
||||||
List<Throwable> errors = new ArrayList<>();
|
|
||||||
for (Long loanId : loanIds) {
|
|
||||||
log.info("recalculateInterest: Loan ID = {}", loanId);
|
|
||||||
Integer numberOfRetries = 0;
|
|
||||||
while (numberOfRetries <= maxNumberOfRetries) {
|
|
||||||
try {
|
|
||||||
this.loanWritePlatformService.recalculateInterest(loanId);
|
|
||||||
numberOfRetries = maxNumberOfRetries + 1;
|
|
||||||
} catch (CannotAcquireLockException | ObjectOptimisticLockingFailureException exception) {
|
|
||||||
log.info("Recalulate interest job has been retried {} time(s)", numberOfRetries);
|
|
||||||
// Fail if the transaction has been retried for
|
|
||||||
// maxNumberOfRetries
|
|
||||||
if (numberOfRetries >= maxNumberOfRetries) {
|
|
||||||
log.error("Recalulate interest job has been retried for the max allowed attempts of {} and will be rolled back",
|
|
||||||
numberOfRetries);
|
|
||||||
errors.add(exception);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Else sleep for a random time (between 1 to 10
|
|
||||||
// seconds) and continue
|
|
||||||
try {
|
|
||||||
int randomNum = RANDOM.nextInt(maxIntervalBetweenRetries + 1);
|
|
||||||
Thread.sleep(1000 + (randomNum * 1000));
|
|
||||||
numberOfRetries = numberOfRetries + 1;
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
log.error("Interest recalculation for loans retry failed due to InterruptedException", e);
|
|
||||||
errors.add(e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Interest recalculation for loans failed for account {}", loanId, e);
|
|
||||||
numberOfRetries = maxNumberOfRetries + 1;
|
|
||||||
errors.add(e);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
log.info("recalculateInterest: Loans count {}", i);
|
|
||||||
}
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
throw new JobExecutionException(errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.RECALCULATE_INTEREST_FOR_LOAN)
|
|
||||||
public void recalculateInterest(Map<String, String> jobParameters) {
|
|
||||||
// gets the officeId
|
|
||||||
final String officeId = jobParameters.get("officeId");
|
|
||||||
log.info("recalculateInterest: officeId={}", 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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<Long> 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();
|
|
||||||
log.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<Long> loanIds, int threadPoolSize, int batchSize, final ExecutorService executorService) {
|
|
||||||
|
|
||||||
List<Callable<Void>> posters = new ArrayList<>();
|
|
||||||
int fromIndex = 0;
|
|
||||||
// get the size of current paginated dataset
|
|
||||||
int size = loanIds.size();
|
|
||||||
// calculate the batch size
|
|
||||||
double toGetCeilValue = size / threadPoolSize;
|
|
||||||
batchSize = (int) Math.ceil(toGetCeilValue);
|
|
||||||
|
|
||||||
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<Long> subList = safeSubList(loanIds, fromIndex, toIndex);
|
|
||||||
RecalculateInterestPoster poster = (RecalculateInterestPoster) this.applicationContext.getBean("recalculateInterestPoster");
|
|
||||||
poster.setLoanIds(subList);
|
|
||||||
poster.setLoanWritePlatformService(loanWritePlatformService);
|
|
||||||
posters.add(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<Future<Void>> responses = executorService.invokeAll(posters);
|
|
||||||
checkCompletion(responses);
|
|
||||||
} catch (InterruptedException e1) {
|
|
||||||
log.error("Interrupted while recalculateInterest", e1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// break the lists into sub lists
|
|
||||||
private <T> List<T> safeSubList(List<T> 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<Future<Void>> responses) {
|
|
||||||
try {
|
|
||||||
for (Future<Void> f : responses) {
|
|
||||||
f.get();
|
|
||||||
}
|
|
||||||
boolean allThreadsExecuted = false;
|
|
||||||
int noOfThreadsExecuted = 0;
|
|
||||||
for (Future<Void> future : responses) {
|
|
||||||
if (future.isDone()) {
|
|
||||||
noOfThreadsExecuted++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allThreadsExecuted = noOfThreadsExecuted == responses.size();
|
|
||||||
if (!allThreadsExecuted) {
|
|
||||||
log.error("All threads could not execute.");
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e1) {
|
|
||||||
log.error("Interrupted while posting IR entries", e1);
|
|
||||||
} catch (ExecutionException e2) {
|
|
||||||
log.error("Execution exception while posting IR entries", e2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-5
@@ -23,7 +23,6 @@ import java.util.Collection;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
|
||||||
import org.apache.fineract.organisation.office.domain.Office;
|
import org.apache.fineract.organisation.office.domain.Office;
|
||||||
import org.apache.fineract.organisation.staff.domain.Staff;
|
import org.apache.fineract.organisation.staff.domain.Staff;
|
||||||
import org.apache.fineract.portfolio.calendar.domain.Calendar;
|
import org.apache.fineract.portfolio.calendar.domain.Calendar;
|
||||||
@@ -78,8 +77,6 @@ public interface LoanWritePlatformService {
|
|||||||
void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances,
|
void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances,
|
||||||
Boolean reschedulebasedOnMeetingDates, LocalDate presentMeetingDate, LocalDate newMeetingDate);
|
Boolean reschedulebasedOnMeetingDates, LocalDate presentMeetingDate, LocalDate newMeetingDate);
|
||||||
|
|
||||||
void applyHolidaysToLoans();
|
|
||||||
|
|
||||||
LoanTransaction initiateLoanTransfer(Loan loan, LocalDate transferDate);
|
LoanTransaction initiateLoanTransfer(Loan loan, LocalDate transferDate);
|
||||||
|
|
||||||
LoanTransaction withdrawLoanTransfer(Loan loan, LocalDate transferDate);
|
LoanTransaction withdrawLoanTransfer(Loan loan, LocalDate transferDate);
|
||||||
@@ -90,8 +87,6 @@ public interface LoanWritePlatformService {
|
|||||||
|
|
||||||
CommandProcessingResult payLoanCharge(Long loanId, Long loanChargeId, JsonCommand command, boolean isChargeIdIncludedInJson);
|
CommandProcessingResult payLoanCharge(Long loanId, Long loanChargeId, JsonCommand command, boolean isChargeIdIncludedInJson);
|
||||||
|
|
||||||
void transferFeeCharges() throws JobExecutionException;
|
|
||||||
|
|
||||||
CommandProcessingResult undoWriteOff(Long loanId);
|
CommandProcessingResult undoWriteOff(Long loanId);
|
||||||
|
|
||||||
CommandProcessingResult updateDisbursementDateAndAmountForTranche(Long loanId, Long disbursementId, JsonCommand command);
|
CommandProcessingResult updateDisbursementDateAndAmountForTranche(Long loanId, Long disbursementId, JsonCommand command);
|
||||||
|
|||||||
-110
@@ -54,9 +54,6 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
|
|||||||
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
|
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
|
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
|
||||||
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
|
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
|
||||||
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.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
||||||
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
|
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
|
||||||
@@ -121,7 +118,6 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarRepository;
|
|||||||
import org.apache.fineract.portfolio.calendar.domain.CalendarType;
|
import org.apache.fineract.portfolio.calendar.domain.CalendarType;
|
||||||
import org.apache.fineract.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
|
import org.apache.fineract.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
|
||||||
import org.apache.fineract.portfolio.charge.domain.Charge;
|
import org.apache.fineract.portfolio.charge.domain.Charge;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
|
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeUpdatedException;
|
import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeUpdatedException;
|
||||||
import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
|
import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
|
||||||
@@ -150,9 +146,7 @@ import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||||
import org.apache.fineract.portfolio.loanaccount.command.LoanUpdateCommand;
|
import org.apache.fineract.portfolio.loanaccount.command.LoanUpdateCommand;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
|
import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
|
||||||
@@ -2032,66 +2026,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.TRANSFER_FEE_CHARGE_FOR_LOANS)
|
|
||||||
public void transferFeeCharges() throws JobExecutionException {
|
|
||||||
final Collection<LoanChargeData> chargeDatas = this.loanChargeReadPlatformService
|
|
||||||
.retrieveLoanChargesForFeePayment(ChargePaymentMode.ACCOUNT_TRANSFER.getValue(), LoanStatus.ACTIVE.getValue());
|
|
||||||
final boolean isRegularTransaction = true;
|
|
||||||
List<Throwable> errors = new ArrayList<>();
|
|
||||||
if (chargeDatas != null) {
|
|
||||||
for (final LoanChargeData chargeData : chargeDatas) {
|
|
||||||
if (chargeData.isInstallmentFee()) {
|
|
||||||
final Collection<LoanInstallmentChargeData> chargePerInstallments = this.loanChargeReadPlatformService
|
|
||||||
.retrieveInstallmentLoanCharges(chargeData.getId(), true);
|
|
||||||
PortfolioAccountData portfolioAccountData = null;
|
|
||||||
for (final LoanInstallmentChargeData installmentChargeData : chargePerInstallments) {
|
|
||||||
if (!installmentChargeData.getDueDate().isAfter(DateUtils.getBusinessLocalDate())) {
|
|
||||||
if (portfolioAccountData == null) {
|
|
||||||
portfolioAccountData = this.accountAssociationsReadPlatformService
|
|
||||||
.retriveLoanLinkedAssociation(chargeData.getLoanId());
|
|
||||||
}
|
|
||||||
final SavingsAccount fromSavingsAccount = null;
|
|
||||||
final boolean isExceptionForBalanceCheck = false;
|
|
||||||
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(DateUtils.getBusinessLocalDate(),
|
|
||||||
installmentChargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
|
|
||||||
portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null,
|
|
||||||
LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(),
|
|
||||||
installmentChargeData.getInstallmentNumber(), AccountTransferType.CHARGE_PAYMENT.getValue(), null, null,
|
|
||||||
null, null, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
|
|
||||||
transferFeeCharge(accountTransferDTO, errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (chargeData.getDueDate() != null && !chargeData.getDueDate().isAfter(DateUtils.getBusinessLocalDate())) {
|
|
||||||
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
|
|
||||||
.retriveLoanLinkedAssociation(chargeData.getLoanId());
|
|
||||||
final SavingsAccount fromSavingsAccount = null;
|
|
||||||
final boolean isExceptionForBalanceCheck = false;
|
|
||||||
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(DateUtils.getBusinessLocalDate(),
|
|
||||||
chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
|
|
||||||
portfolioAccountData.accountId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null,
|
|
||||||
LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), null,
|
|
||||||
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount,
|
|
||||||
isRegularTransaction, isExceptionForBalanceCheck);
|
|
||||||
transferFeeCharge(accountTransferDTO, errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
throw new JobExecutionException(errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void transferFeeCharge(final AccountTransferDTO accountTransferDTO, List<Throwable> errors) {
|
|
||||||
try {
|
|
||||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
log.error("Exception while paying charge {} for loan id {}", accountTransferDTO.getChargeId(),
|
|
||||||
accountTransferDTO.getToAccountId(), e);
|
|
||||||
errors.add(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private LoanCharge retrieveLoanChargeBy(final Long loanId, final Long loanChargeId) {
|
private LoanCharge retrieveLoanChargeBy(final Long loanId, final Long loanChargeId) {
|
||||||
final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId)
|
final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId)
|
||||||
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
|
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
|
||||||
@@ -2524,50 +2458,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
this.loanRepositoryWrapper.save(loansToUpdate);
|
this.loanRepositoryWrapper.save(loansToUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
@CronTarget(jobName = JobName.APPLY_HOLIDAYS_TO_LOANS)
|
|
||||||
public void applyHolidaysToLoans() {
|
|
||||||
|
|
||||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
|
||||||
|
|
||||||
if (!isHolidayEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Collection<Integer> loanStatuses = new ArrayList<>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
|
|
||||||
LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue()));
|
|
||||||
// Get all Holidays which are active and not processed
|
|
||||||
final List<Holiday> holidays = this.holidayRepository.findUnprocessed();
|
|
||||||
|
|
||||||
// Loop through all holidays
|
|
||||||
for (final Holiday holiday : holidays) {
|
|
||||||
// All offices to which holiday is applied
|
|
||||||
final Set<Office> offices = holiday.getOffices();
|
|
||||||
final Collection<Long> officeIds = new ArrayList<>(offices.size());
|
|
||||||
for (final Office office : offices) {
|
|
||||||
officeIds.add(office.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all loans
|
|
||||||
final List<Loan> loans = new ArrayList<>();
|
|
||||||
// get all individual and jlg loans
|
|
||||||
loans.addAll(this.loanRepositoryWrapper.findByClientOfficeIdsAndLoanStatus(officeIds, loanStatuses));
|
|
||||||
// FIXME: AA optimize to get all client and group loans belongs to a
|
|
||||||
// office id
|
|
||||||
// get all group loans
|
|
||||||
loans.addAll(this.loanRepositoryWrapper.findByGroupOfficeIdsAndLoanStatus(officeIds, loanStatuses));
|
|
||||||
|
|
||||||
for (final Loan loan : loans) {
|
|
||||||
// apply holiday
|
|
||||||
loan.applyHolidayToRepaymentScheduleDates(holiday, this.loanUtilService);
|
|
||||||
}
|
|
||||||
this.loanRepositoryWrapper.save(loans);
|
|
||||||
holiday.processed();
|
|
||||||
}
|
|
||||||
this.holidayRepository.save(holidays);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkClientOrGroupActive(final Loan loan) {
|
private void checkClientOrGroupActive(final Loan loan) {
|
||||||
final Client client = loan.client();
|
final Client client = loan.client();
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* 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.savings.jobs.applyannualfeeforsavings;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ApplyAnnualFeeForSavingsConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
|
||||||
|
@Autowired
|
||||||
|
private SavingsAccountWritePlatformService savingsAccountWritePlatformService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step applyAnnualFeeForSavingsStep() {
|
||||||
|
return steps.get(JobName.APPLY_ANNUAL_FEE_FOR_SAVINGS.name()).tasklet(applyAnnualFeeForSavingsTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job applyAnnualFeeForSavingsJob() {
|
||||||
|
return jobs.get(JobName.APPLY_ANNUAL_FEE_FOR_SAVINGS.name()).start(applyAnnualFeeForSavingsStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApplyAnnualFeeForSavingsTasklet applyAnnualFeeForSavingsTasklet() {
|
||||||
|
return new ApplyAnnualFeeForSavingsTasklet(savingsAccountChargeReadPlatformService, savingsAccountWritePlatformService);
|
||||||
|
}
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* 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.savings.jobs.applyannualfeeforsavings;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.SavingsAccountAnnualFeeData;
|
||||||
|
import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||||
|
import org.springframework.batch.core.StepContribution;
|
||||||
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ApplyAnnualFeeForSavingsTasklet implements Tasklet {
|
||||||
|
|
||||||
|
private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
|
||||||
|
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
final Collection<SavingsAccountAnnualFeeData> annualFeeData = savingsAccountChargeReadPlatformService
|
||||||
|
.retrieveChargesWithAnnualFeeDue();
|
||||||
|
|
||||||
|
for (final SavingsAccountAnnualFeeData savingsAccountReference : annualFeeData) {
|
||||||
|
try {
|
||||||
|
savingsAccountWritePlatformService.applyAnnualFee(savingsAccountReference.getId(), savingsAccountReference.getAccountId());
|
||||||
|
} catch (final PlatformApiDataValidationException e) {
|
||||||
|
final List<ApiParameterError> errors = e.getErrors();
|
||||||
|
for (final ApiParameterError error : errors) {
|
||||||
|
log.error("Apply annual fee failed for account: {} with message {}", savingsAccountReference.getAccountNo(), error);
|
||||||
|
}
|
||||||
|
} catch (final Exception ex) {
|
||||||
|
log.error("Apply annual fee failed for account: {}", savingsAccountReference.getAccountNo(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("{}: Records affected by applyAnnualFeeForSavings: {}", ThreadLocalContextUtil.getTenant().getName(),
|
||||||
|
annualFeeData.size());
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* 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.savings.jobs.generateadhocclientschhedule;
|
||||||
|
|
||||||
|
import org.apache.fineract.adhocquery.service.AdHocReadPlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
|
import org.springframework.batch.core.Job;
|
||||||
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||||
|
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||||
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class GenerateAdhocClientScheduleConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JobBuilderFactory jobs;
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory steps;
|
||||||
|
@Autowired
|
||||||
|
private AdHocReadPlatformService adHocReadPlatformService;
|
||||||
|
@Autowired
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
protected Step generateAdhocClientScheduleStep() {
|
||||||
|
return steps.get(JobName.GENERATE_ADHOC_CLIENT_SCHEDULE.name()).tasklet(generateAdhocClientScheduleTasklet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Job generateAdhocClientScheduleJob() {
|
||||||
|
return jobs.get(JobName.GENERATE_ADHOC_CLIENT_SCHEDULE.name()).start(generateAdhocClientScheduleStep())
|
||||||
|
.incrementer(new RunIdIncrementer()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GenerateAdhocClientScheduleTasklet generateAdhocClientScheduleTasklet() {
|
||||||
|
return new GenerateAdhocClientScheduleTasklet(adHocReadPlatformService, jdbcTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user