[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
|
||||
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'
|
||||
}
|
||||
|
||||
implementation ('org.springframework.boot:spring-boot-starter-batch') {
|
||||
}
|
||||
|
||||
runtimeOnly('org.glassfish.jaxb:jaxb-runtime') {
|
||||
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.serialization.FromJsonHelper;
|
||||
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.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
@@ -66,7 +64,6 @@ public class JournalEntryRunningBalanceUpdateServiceImpl implements JournalEntry
|
||||
private final GLJournalEntryMapper entryMapper = new GLJournalEntryMapper();
|
||||
|
||||
@Override
|
||||
@CronTarget(jobName = JobName.ACCOUNTING_RUNNING_BALANCE_UPDATE)
|
||||
public void updateRunningBalance() {
|
||||
String dateFinder = "select MIN(je.entry_date) as entityDate from acc_gl_journal_entry je "
|
||||
+ "where je.is_running_balance_calculated=false ";
|
||||
|
||||
+4
-2
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
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.data.CommandProcessingResult;
|
||||
|
||||
@@ -25,9 +27,9 @@ public interface ProvisioningEntriesWritePlatformService {
|
||||
|
||||
CommandProcessingResult createProvisioningEntries(JsonCommand command);
|
||||
|
||||
ProvisioningEntry createProvisioningEntry(LocalDate date, boolean addJournalEntries);
|
||||
|
||||
CommandProcessingResult reCreateProvisioningEntries(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.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.CommandProcessingResultBuilder;
|
||||
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.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
@@ -129,27 +125,6 @@ public class ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl implements
|
||||
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
|
||||
public CommandProcessingResult createProvisioningEntries(JsonCommand command) {
|
||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
||||
@@ -161,29 +136,27 @@ public class ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl implements
|
||||
if (criteriaCollection == null || criteriaCollection.size() == 0) {
|
||||
throw new NoProvisioningCriteriaDefinitionFound();
|
||||
}
|
||||
ProvisioningEntry requestedEntry = createProvsioningEntry(createdDate, addJournalEntries);
|
||||
ProvisioningEntry requestedEntry = createProvisioningEntry(createdDate, addJournalEntries);
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(requestedEntry.getId()).build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException e) {
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private ProvisioningEntry createProvsioningEntry(LocalDate date, boolean addJournalEntries) {
|
||||
@Override
|
||||
public ProvisioningEntry createProvisioningEntry(LocalDate date, boolean addJournalEntries) {
|
||||
ProvisioningEntry existingEntry = this.provisioningEntryRepository.findByProvisioningEntryDate(date);
|
||||
if (existingEntry != null) {
|
||||
throw new ProvisioningEntryAlreadyCreatedException(existingEntry.getId(), existingEntry.getCreatedDate());
|
||||
}
|
||||
AppUser currentUser = this.platformSecurityContext.authenticatedUser();
|
||||
AppUser lastModifiedBy = null;
|
||||
LocalDate lastModifiedDate = null;
|
||||
Set<LoanProductProvisioningEntry> nullEntries = null;
|
||||
ProvisioningEntry requestedEntry = new ProvisioningEntry(currentUser, date, lastModifiedBy, lastModifiedDate, nullEntries);
|
||||
ProvisioningEntry requestedEntry = new ProvisioningEntry(currentUser, date, null, null, null);
|
||||
Collection<LoanProductProvisioningEntry> entries = generateLoanProvisioningEntry(requestedEntry, date);
|
||||
requestedEntry.setProvisioningEntries(entries);
|
||||
if (addJournalEntries) {
|
||||
ProvisioningEntryData exisProvisioningEntryData = this.provisioningEntriesReadPlatformService
|
||||
ProvisioningEntryData existingProvisioningEntryData = this.provisioningEntriesReadPlatformService
|
||||
.retrieveExistingProvisioningIdDateWithJournals();
|
||||
revertAndAddJournalEntries(exisProvisioningEntryData, requestedEntry);
|
||||
revertAndAddJournalEntries(existingProvisioningEntryData, requestedEntry);
|
||||
} else {
|
||||
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.MediaType;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.adhocquery.data.AdHocData;
|
||||
import org.apache.fineract.adhocquery.service.AdHocReadPlatformService;
|
||||
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.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -53,6 +53,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Tag(name = "AdhocQuery Api", description = "")
|
||||
@RequiredArgsConstructor
|
||||
public class AdHocApiResource {
|
||||
|
||||
/**
|
||||
@@ -67,17 +68,6 @@ public class AdHocApiResource {
|
||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||
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
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@@ -105,10 +95,7 @@ public class AdHocApiResource {
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String createAdHocQuery(final String apiRequestBodyAsJson) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.createAdHoc() //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createAdHoc().withJson(apiRequestBodyAsJson).build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
@@ -137,10 +124,7 @@ public class AdHocApiResource {
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String update(@PathParam("adHocId") @Parameter(description = "adHocId") final Long adHocId, final String apiRequestBodyAsJson) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.updateAdHoc(adHocId) //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateAdHoc(adHocId).withJson(apiRequestBodyAsJson).build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
@@ -159,9 +143,7 @@ public class AdHocApiResource {
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String deleteAdHocQuery(@PathParam("adHocId") @Parameter(description = "adHocId") final Long adHocId) {
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.deleteAdHoc(adHocId) //
|
||||
.build();
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteAdHoc(adHocId).build();
|
||||
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
|
||||
+2
-5
@@ -55,18 +55,15 @@ public class AdHocReadPlatformServiceImpl implements AdHocReadPlatformService {
|
||||
|
||||
@Override
|
||||
public Collection<AdHocData> retrieveAllActiveAdHocQuery() {
|
||||
final String sql = "select " + this.adHocRowMapper.schema() + " where r." + sqlGenerator.escape("is_active")
|
||||
+ " = true order by r.id";
|
||||
final String sql = "select " + adHocRowMapper.schema() + " where r." + sqlGenerator.escape("is_active") + " = true order by r.id";
|
||||
|
||||
return this.jdbcTemplate.query(sql, this.adHocRowMapper); // NOSONAR
|
||||
return jdbcTemplate.query(sql, adHocRowMapper); // NOSONAR
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdHocData retrieveOne(final Long id) {
|
||||
|
||||
try {
|
||||
final String sql = "select " + this.adHocRowMapper.schema() + " where r.id=?";
|
||||
|
||||
return this.jdbcTemplate.queryForObject(sql, this.adHocRowMapper, new Object[] { id }); // NOSONAR
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AdHocNotFoundException(id, e);
|
||||
|
||||
+8
-25
@@ -19,6 +19,8 @@
|
||||
package org.apache.fineract.adhocquery.service;
|
||||
|
||||
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.AdHocRepository;
|
||||
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.exception.PlatformDataIntegrityException;
|
||||
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.NonTransientDataAccessException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
@@ -37,22 +36,14 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePlatformService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AdHocWritePlatformServiceJpaRepositoryImpl.class);
|
||||
private final PlatformSecurityContext context;
|
||||
private final AdHocRepository adHocRepository;
|
||||
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
|
||||
@Override
|
||||
public CommandProcessingResult createAdHocQuery(final JsonCommand command) {
|
||||
@@ -69,9 +60,7 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
final Throwable throwable = dve.getMostSpecificCause();
|
||||
handleDataIntegrityIssues(command, throwable, dve);
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.build();
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +77,7 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
|
||||
"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",
|
||||
"Unknown data integrity issue with resource.");
|
||||
}
|
||||
@@ -108,17 +97,11 @@ public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePla
|
||||
this.adHocRepository.saveAndFlush(adHoc);
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(adHocId) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(adHocId).with(changes).build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
final Throwable throwable = dve.getMostSpecificCause();
|
||||
handleDataIntegrityIssues(command, throwable, dve);
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.build();
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
@@ -26,6 +28,8 @@ public interface BusinessDateWritePlatformService {
|
||||
|
||||
CommandProcessingResult updateBusinessDate(JsonCommand command);
|
||||
|
||||
void adjustDate(BusinessDateData data, Map<String, Object> changes);
|
||||
|
||||
void increaseCOBDateByOneDay() 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.PlatformApiDataValidationException;
|
||||
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.service.JobName;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@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 isBusinessDateEnabled = configurationDomainService.isBusinessDateEnabled();
|
||||
|
||||
@@ -80,21 +79,19 @@ public class BusinessDateWritePlatformServiceImpl implements BusinessDateWritePl
|
||||
}
|
||||
|
||||
@Override
|
||||
@CronTarget(jobName = JobName.INCREASE_COB_DATE_BY_1_DAY)
|
||||
public void increaseCOBDateByOneDay() throws JobExecutionException {
|
||||
increaseDateByTypeByOneDay(BusinessDateType.COB_DATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CronTarget(jobName = JobName.INCREASE_BUSINESS_DATE_BY_1_DAY)
|
||||
public void increaseBusinessDateByOneDay() throws JobExecutionException {
|
||||
increaseDateByTypeByOneDay(BusinessDateType.BUSINESS_DATE);
|
||||
}
|
||||
|
||||
private void increaseDateByTypeByOneDay(BusinessDateType businessDateType) throws JobExecutionException {
|
||||
List<Throwable> exceptions = new ArrayList<>();
|
||||
Map<String, Object> changes = new HashMap<>();
|
||||
Optional<BusinessDate> businessDateEntity = repository.findByType(businessDateType);
|
||||
List<Throwable> exceptions = new ArrayList<>();
|
||||
|
||||
LocalDate businessDate = businessDateEntity.map(BusinessDate::getDate).orElse(DateUtils.getLocalDateOfTenant());
|
||||
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());
|
||||
exceptions.add(e);
|
||||
}
|
||||
|
||||
if (!exceptions.isEmpty()) {
|
||||
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.MediaType;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.accounting.journalentry.api.DateParam;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
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.SearchParameters;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -54,32 +54,22 @@ import org.springframework.stereotype.Component;
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@RequiredArgsConstructor
|
||||
public class EmailApiResource {
|
||||
|
||||
private final String resourceNameForPermissions = "Email";
|
||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "Email";
|
||||
private final PlatformSecurityContext context;
|
||||
private final EmailReadPlatformService readPlatformService;
|
||||
private final DefaultToApiJsonSerializer<EmailData> toApiJsonSerializer;
|
||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||
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
|
||||
public String retrieveAllEmails(@Context final UriInfo uriInfo) {
|
||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
||||
final Collection<EmailData> emailMessages = this.readPlatformService.retrieveAll();
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, emailMessages);
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
final Collection<EmailData> emailMessages = readPlatformService.retrieveAll();
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, emailMessages);
|
||||
}
|
||||
|
||||
@GET
|
||||
@@ -88,11 +78,11 @@ public class EmailApiResource {
|
||||
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
||||
@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);
|
||||
Collection<EmailData> emailMessages = this.readPlatformService.retrieveAllPending(searchParameters);
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, emailMessages);
|
||||
Collection<EmailData> emailMessages = readPlatformService.retrieveAllPending(searchParameters);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, emailMessages);
|
||||
}
|
||||
|
||||
@GET
|
||||
@@ -101,13 +91,13 @@ public class EmailApiResource {
|
||||
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
||||
@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);
|
||||
Collection<EmailData> emailMessages = this.readPlatformService.retrieveAllSent(searchParameters);
|
||||
Collection<EmailData> emailMessages = readPlatformService.retrieveAllSent(searchParameters);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, emailMessages);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, emailMessages);
|
||||
}
|
||||
|
||||
@GET
|
||||
@@ -118,7 +108,7 @@ public class EmailApiResource {
|
||||
@QueryParam("fromDate") final DateParam fromDateParam, @QueryParam("toDate") final DateParam toDateParam,
|
||||
@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;
|
||||
if (fromDateParam != null) {
|
||||
fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
|
||||
@@ -127,9 +117,9 @@ public class EmailApiResource {
|
||||
if (toDateParam != null) {
|
||||
toDate = toDateParam.getDate("toDate", dateFormat, locale);
|
||||
}
|
||||
Page<EmailData> emailMessages = this.readPlatformService.retrieveEmailByStatus(limit, status, fromDate, toDate);
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, emailMessages);
|
||||
Page<EmailData> emailMessages = readPlatformService.retrieveEmailByStatus(limit, status, fromDate, toDate);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, emailMessages);
|
||||
}
|
||||
|
||||
@GET
|
||||
@@ -138,13 +128,13 @@ public class EmailApiResource {
|
||||
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
||||
@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);
|
||||
Collection<EmailData> emailMessages = this.readPlatformService.retrieveAllFailed(searchParameters);
|
||||
Collection<EmailData> emailMessages = readPlatformService.retrieveAllFailed(searchParameters);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, emailMessages);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, emailMessages);
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -152,19 +142,19 @@ public class EmailApiResource {
|
||||
|
||||
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
|
||||
@Path("{resourceId}")
|
||||
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());
|
||||
return this.toApiJsonSerializer.serialize(settings, emailMessage);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, emailMessage);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@@ -173,9 +163,9 @@ public class EmailApiResource {
|
||||
|
||||
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
|
||||
@@ -184,8 +174,8 @@ public class EmailApiResource {
|
||||
|
||||
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.MediaType;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
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.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -62,10 +62,10 @@ import org.springframework.stereotype.Component;
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@RequiredArgsConstructor
|
||||
public class EmailCampaignApiResource {
|
||||
|
||||
// change name to email campaign
|
||||
private final String resourceNameForPermissions = "EMAIL_CAMPAIGN";
|
||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "EMAIL_CAMPAIGN";
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
@@ -82,35 +82,15 @@ public class EmailCampaignApiResource {
|
||||
|
||||
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
|
||||
@Path("{resourceId}")
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
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);
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.emailCampaignDataDefaultToApiJsonSerializer.serialize(settings, emailCampaignData);
|
||||
EmailCampaignData emailCampaignData = emailCampaignReadPlatformService.retrieveOne(resourceId);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return emailCampaignDataDefaultToApiJsonSerializer.serialize(settings, emailCampaignData);
|
||||
|
||||
}
|
||||
|
||||
@@ -118,12 +98,12 @@ public class EmailCampaignApiResource {
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
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());
|
||||
return this.emailCampaignDataDefaultToApiJsonSerializer.serialize(settings, emailCampaignDataCollection);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return emailCampaignDataDefaultToApiJsonSerializer.serialize(settings, emailCampaignDataCollection);
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -133,9 +113,9 @@ public class EmailCampaignApiResource {
|
||||
|
||||
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
|
||||
@@ -148,9 +128,9 @@ public class EmailCampaignApiResource {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateEmailCampaign(campaignId).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);
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -162,18 +142,18 @@ public class EmailCampaignApiResource {
|
||||
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
|
||||
|
||||
CommandProcessingResult result = null;
|
||||
CommandWrapper commandRequest = null;
|
||||
CommandWrapper commandRequest;
|
||||
if (is(commandParam, "activate")) {
|
||||
commandRequest = builder.activateEmailCampaign(campaignId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
} else if (is(commandParam, "close")) {
|
||||
commandRequest = builder.closeEmailCampaign(campaignId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
} else if (is(commandParam, "reactivate")) {
|
||||
commandRequest = builder.reactivateEmailCampaign(campaignId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
return toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -181,36 +161,36 @@ public class EmailCampaignApiResource {
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
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;
|
||||
final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
|
||||
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
|
||||
campaignMessage = this.emailCampaignWritePlatformService.previewMessage(query);
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.previewCampaignMessageDefaultToApiJsonSerializer.serialize(settings, campaignMessage, new HashSet<String>());
|
||||
PreviewCampaignMessage campaignMessage;
|
||||
final JsonElement parsedQuery = fromJsonHelper.parse(apiRequestBodyAsJson);
|
||||
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, fromJsonHelper);
|
||||
campaignMessage = emailCampaignWritePlatformService.previewMessage(query);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return previewCampaignMessageDefaultToApiJsonSerializer.serialize(settings, campaignMessage, new HashSet<>());
|
||||
|
||||
}
|
||||
|
||||
@GET()
|
||||
@Path("template")
|
||||
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());
|
||||
return this.toApiJsonSerializer.serialize(settings, emailBusinessRulesDataCollection);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, emailBusinessRulesDataCollection);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("template/{resourceId}")
|
||||
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 ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, emailBusinessRulesData);
|
||||
final EmailBusinessRulesData emailBusinessRulesData = emailCampaignReadPlatformService.retrieveOneTemplate(resourceId);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, emailBusinessRulesData);
|
||||
|
||||
}
|
||||
|
||||
@@ -220,9 +200,9 @@ public class EmailCampaignApiResource {
|
||||
|
||||
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) {
|
||||
|
||||
+8
-355
@@ -24,9 +24,6 @@ import com.github.mustachejava.DefaultMustacheFactory;
|
||||
import com.github.mustachejava.Mustache;
|
||||
import com.github.mustachejava.MustacheFactory;
|
||||
import com.google.gson.Gson;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
@@ -36,8 +33,6 @@ import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -46,17 +41,14 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.mail.internet.AddressException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.campaigns.email.data.EmailCampaignData;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.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.exception.EmailCampaignMustBeClosedToBeDeletedException;
|
||||
import org.apache.fineract.infrastructure.campaigns.email.exception.EmailCampaignMustBeClosedToEditException;
|
||||
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.service.GenericDataService;
|
||||
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.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
@@ -100,47 +81,19 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampaignWritePlatformService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(EmailCampaignWritePlatformCommandHandlerImpl.class);
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
private final EmailCampaignRepository emailCampaignRepository;
|
||||
private final EmailCampaignValidator emailCampaignValidator;
|
||||
private final EmailCampaignReadPlatformService emailCampaignReadPlatformService;
|
||||
private final ReportRepository reportRepository;
|
||||
private final EmailMessageRepository emailMessageRepository;
|
||||
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
||||
private final ReadReportingService readReportingService;
|
||||
private final GenericDataService genericDataService;
|
||||
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
|
||||
@Override
|
||||
@@ -178,10 +131,7 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai
|
||||
|
||||
this.emailCampaignRepository.saveAndFlush(emailCampaign);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(emailCampaign.getId()) //
|
||||
.build();
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(emailCampaign.getId()).build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -208,11 +158,7 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai
|
||||
if (!changes.isEmpty()) {
|
||||
this.emailCampaignRepository.saveAndFlush(emailCampaign);
|
||||
}
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(resourceId) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(resourceId).with(changes).build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
final Throwable throwable = dve.getMostSpecificCause();
|
||||
handleDataIntegrityIssues(command, throwable, dve);
|
||||
@@ -319,56 +265,6 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai
|
||||
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
|
||||
@Override
|
||||
public CommandProcessingResult activateEmailCampaign(Long campaignId, JsonCommand command) {
|
||||
@@ -456,7 +352,8 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai
|
||||
}
|
||||
|
||||
@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 {
|
||||
final String reportType = "report";
|
||||
|
||||
@@ -585,248 +482,4 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai
|
||||
}
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
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.domain.EmailCampaign;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonQuery;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
|
||||
public interface EmailCampaignWritePlatformService {
|
||||
@@ -41,12 +43,11 @@ public interface EmailCampaignWritePlatformService {
|
||||
|
||||
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);
|
||||
|
||||
void sendEmailMessage() throws JobExecutionException;
|
||||
|
||||
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
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
package org.apache.fineract.infrastructure.campaigns.jobs.updateemailoutboundwithcampaignmessage;
|
||||
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
public class EmailParamMappingException extends RuntimeException {
|
||||
|
||||
public interface LoanSchedularService {
|
||||
|
||||
void applyChargeForOverdueLoans() throws JobExecutionException;
|
||||
|
||||
void recalculateInterest() throws JobExecutionException;
|
||||
|
||||
void recalculateInterest(@SuppressWarnings("unused") Map<String, String> jobParameters);
|
||||
public EmailParamMappingException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
+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.MediaType;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
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.SearchParameters;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/smscampaigns")
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@RequiredArgsConstructor
|
||||
public class SmsCampaignApiResource {
|
||||
|
||||
private final PlatformSecurityContext platformSecurityContext;
|
||||
@@ -74,81 +75,80 @@ public class SmsCampaignApiResource {
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
private final DefaultToApiJsonSerializer<CampaignPreviewData> previewCampaignMessageDefaultToApiJsonSerializer;
|
||||
private final SmsCampaignWritePlatformService smsCampaignWritePlatformService;
|
||||
|
||||
private final String resourceNameForPermissions = "SMS_CAMPAIGN";
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
@Autowired
|
||||
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;
|
||||
}
|
||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "SMS_CAMPAIGN";
|
||||
|
||||
@GET
|
||||
@Path("template")
|
||||
@Operation(summary = "Retrieve a SMS Campaign", description = "Example Requests:\n" + "\n" + "smscampaigns/1\n" + "\n" + "\n"
|
||||
+ "smscampaigns/1?template=true\n" + "\n" + "\n" + "smscampaigns/template")
|
||||
@Operation(summary = "Retrieve a SMS Campaign", description = """
|
||||
Example Requests:
|
||||
|
||||
smscampaigns/1
|
||||
|
||||
|
||||
smscampaigns/1?template=true
|
||||
|
||||
|
||||
smscampaigns/template""")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SmsCampaignData.class)))
|
||||
public String template(@Context final UriInfo uriInfo) {
|
||||
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);
|
||||
final SmsCampaignData smsCampaignData = this.smsCampaignReadPlatformService.retrieveTemplate(CampaignType.SMS.name());
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, smsCampaignData);
|
||||
platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);
|
||||
final SmsCampaignData smsCampaignData = smsCampaignReadPlatformService.retrieveTemplate(CampaignType.SMS.name());
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, smsCampaignData);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Create a SMS Campaign", description = "Mandatory Fields\n"
|
||||
+ "campaignName, campaignType, triggerType, providerId, runReportId, message\n" + "\n"
|
||||
+ "Mandatory Fields for Cash based on selected report id\n" + "paramValue in json format")
|
||||
@Operation(summary = "Create a SMS Campaign", description = """
|
||||
Mandatory Fields
|
||||
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)))
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CommandProcessingResult.class)))
|
||||
public String createCampaign(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
this.platformSecurityContext.authenticatedUser();
|
||||
platformSecurityContext.authenticatedUser();
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createSmsCampaign().withJson(apiRequestBodyAsJson).build();
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{resourceId}")
|
||||
@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)))
|
||||
public String retrieveCampaign(@PathParam("resourceId") final Long resourceId, @Context final UriInfo uriInfo) {
|
||||
this.platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);
|
||||
SmsCampaignData smsCampaignData = this.smsCampaignReadPlatformService.retrieveOne(resourceId);
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, smsCampaignData);
|
||||
platformSecurityContext.authenticatedUser().validateHasReadPermission(SmsCampaignConstants.RESOURCE_NAME);
|
||||
SmsCampaignData smsCampaignData = smsCampaignReadPlatformService.retrieveOne(resourceId);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, smsCampaignData);
|
||||
|
||||
}
|
||||
|
||||
@GET
|
||||
@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)))
|
||||
public String retrieveAllEmails(@QueryParam("sqlSearch") final String sqlSearch, @QueryParam("offset") final Integer offset,
|
||||
@QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
|
||||
@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);
|
||||
Page<SmsCampaignData> smsCampaignDataCollection = this.smsCampaignReadPlatformService.retrieveAll(searchParameters);
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, smsCampaignDataCollection);
|
||||
Page<SmsCampaignData> smsCampaignDataCollection = smsCampaignReadPlatformService.retrieveAll(searchParameters);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, smsCampaignDataCollection);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@@ -162,8 +162,8 @@ public class SmsCampaignApiResource {
|
||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSmsCampaign(campaignId).withJson(apiRequestBodyAsJson)
|
||||
.build();
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -176,18 +176,18 @@ public class SmsCampaignApiResource {
|
||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
|
||||
CommandProcessingResult result = null;
|
||||
CommandWrapper commandRequest = null;
|
||||
CommandWrapper commandRequest;
|
||||
if (is(commandParam, "activate")) {
|
||||
commandRequest = builder.activateSmsCampaign(campaignId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
} else if (is(commandParam, "close")) {
|
||||
commandRequest = builder.closeSmsCampaign(campaignId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
} else if (is(commandParam, "reactivate")) {
|
||||
commandRequest = builder.reactivateSmsCampaign(campaignId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
return toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -195,14 +195,14 @@ public class SmsCampaignApiResource {
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
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;
|
||||
final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
|
||||
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
|
||||
campaignMessage = this.smsCampaignWritePlatformService.previewMessage(query);
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.previewCampaignMessageDefaultToApiJsonSerializer.serialize(settings, campaignMessage, new HashSet<String>());
|
||||
CampaignPreviewData campaignMessage;
|
||||
final JsonElement parsedQuery = fromJsonHelper.parse(apiRequestBodyAsJson);
|
||||
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, fromJsonHelper);
|
||||
campaignMessage = smsCampaignWritePlatformService.previewMessage(query);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
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)))
|
||||
public String delete(@PathParam("campaignId") final Long campaignId) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSmsCampaign(campaignId).build();
|
||||
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
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());
|
||||
if (SmsCampaignTriggerType.fromInt(triggerType.intValue()).isSchedule()) {
|
||||
final Locale locale = command.extractLocale();
|
||||
String dateTimeFormat = null;
|
||||
String dateTimeFormat;
|
||||
if (command.hasParameter(SmsCampaignValidator.dateTimeFormat)) {
|
||||
dateTimeFormat = command.stringValueOfParameterNamed(SmsCampaignValidator.dateTimeFormat);
|
||||
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.JsonQuery;
|
||||
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.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
@@ -43,14 +42,14 @@ public interface SmsCampaignWritePlatformService {
|
||||
|
||||
CommandProcessingResult reactivateSmsCampaign(Long campaignId, JsonCommand command);
|
||||
|
||||
void insertDirectCampaignIntoSmsOutboundTable(SmsCampaign smsCampaign);
|
||||
|
||||
void insertDirectCampaignIntoSmsOutboundTable(Loan loan, SmsCampaign smsCampaign);
|
||||
|
||||
String compileSmsTemplate(String textMessageTemplate, String campaignName, Map<String, Object> smsParams);
|
||||
|
||||
CampaignPreviewData previewMessage(JsonQuery query);
|
||||
|
||||
void storeTemplateMessageIntoSmsOutBoundTable() throws JobExecutionException;
|
||||
|
||||
void insertDirectCampaignIntoSmsOutboundTable(Client client, 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.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignStatus;
|
||||
import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.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.service.GenericDataService;
|
||||
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.sms.domain.SmsMessage;
|
||||
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.savings.domain.SavingsAccount;
|
||||
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.orm.jpa.JpaSystemException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWritePlatformService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SmsCampaignWritePlatformServiceJpaImpl.class);
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
private final SmsCampaignRepository smsCampaignRepository;
|
||||
@@ -108,32 +101,9 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
||||
private final ReadReportingService readReportingService;
|
||||
private final GenericDataService genericDataService;
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
private final DeviceRegistrationRepositoryWrapper deviceRegistrationRepository;
|
||||
|
||||
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
|
||||
@Override
|
||||
public CommandProcessingResult create(JsonCommand command) {
|
||||
@@ -150,10 +120,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
||||
}
|
||||
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(smsCampaign.getId()) //
|
||||
.build();
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(smsCampaign.getId()).build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -210,45 +177,41 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
||||
smsCampaign.delete();
|
||||
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withEntityId(smsCampaign.getId()) //
|
||||
.build();
|
||||
return new CommandProcessingResultBuilder().withEntityId(smsCampaign.getId()).build();
|
||||
|
||||
}
|
||||
|
||||
private void insertDirectCampaignIntoSmsOutboundTable(SmsCampaign smsCampaign) {
|
||||
@Override
|
||||
public void insertDirectCampaignIntoSmsOutboundTable(SmsCampaign smsCampaign) {
|
||||
try {
|
||||
HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(),
|
||||
new TypeReference<HashMap<String, String>>() {});
|
||||
HashMap<String, String> campaignParams = new ObjectMapper().readValue(smsCampaign.getParamValue(), new TypeReference<>() {});
|
||||
|
||||
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);
|
||||
|
||||
if (runReportObject != null) {
|
||||
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");
|
||||
Object mobileNo = entry.get("mobileNo");
|
||||
|
||||
Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId.longValue());
|
||||
if (this.smsCampaignValidator.isValidNotificationOrSms(client, smsCampaign, mobileNo)) {
|
||||
// String countryCode =
|
||||
// this.smsReadPlatformService.retrieveCountryCode(client.getOffice().getId()).getCountryCode();
|
||||
Client client = clientRepositoryWrapper.findOneWithNotFoundDetection(clientId.longValue());
|
||||
if (smsCampaignValidator.isValidNotificationOrSms(client, smsCampaign, mobileNo)) {
|
||||
String mobileNumber = null;
|
||||
if (mobileNo != null) {
|
||||
mobileNumber = mobileNo.toString();
|
||||
}
|
||||
SmsMessage smsMessage = SmsMessage.pendingSms(null, null, client, null, textMessage, mobileNumber, smsCampaign,
|
||||
smsCampaign.isNotification());
|
||||
this.smsMessageRepository.save(smsMessage);
|
||||
smsMessageRepository.save(smsMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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) {
|
||||
LOG.error("Error occured.", e);
|
||||
} catch (final RuntimeException e) {
|
||||
LOG.error("Error occured.", e);
|
||||
} catch (final IOException | RuntimeException e) {
|
||||
log.error("Error occured.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,10 +314,8 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Error occured.", e);
|
||||
} catch (final RuntimeException e) {
|
||||
LOG.error("Error occured.", e);
|
||||
} catch (final IOException | RuntimeException e) {
|
||||
log.error("Error occured.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,52 +355,19 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Error occured.", e);
|
||||
} catch (final RuntimeException e) {
|
||||
LOG.error("Error occured.", e);
|
||||
} catch (final IOException | 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
|
||||
@Override
|
||||
public CommandProcessingResult activateSmsCampaign(Long campaignId, JsonCommand command) {
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
final AppUser currentUser = context.authenticatedUser();
|
||||
|
||||
this.smsCampaignValidator.validateActivation(command.json());
|
||||
|
||||
final SmsCampaign smsCampaign = this.smsCampaignRepository.findById(campaignId)
|
||||
.orElseThrow(() -> new SmsCampaignNotFound(campaignId));
|
||||
final SmsCampaign smsCampaign = smsCampaignRepository.findById(campaignId).orElseThrow(() -> new SmsCampaignNotFound(campaignId));
|
||||
|
||||
final Locale locale = command.extractLocale();
|
||||
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
|
||||
@@ -449,7 +375,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
||||
|
||||
smsCampaign.activate(currentUser, fmt, activationDate);
|
||||
|
||||
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
||||
smsCampaignRepository.saveAndFlush(smsCampaign);
|
||||
|
||||
if (smsCampaign.isDirect()) {
|
||||
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
|
||||
* process for it
|
||||
*/
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(smsCampaign.getId()) //
|
||||
.build();
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(smsCampaign.getId()).build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -529,7 +452,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
||||
final String response = this.genericDataService.generateJsonFromGenericResultsetData(results);
|
||||
resultList = new ObjectMapper().readValue(response, new TypeReference<List<HashMap<String, Object>>>() {});
|
||||
} 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;
|
||||
}
|
||||
// loop changes array date to string date
|
||||
@@ -628,30 +551,7 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
||||
}
|
||||
this.smsCampaignRepository.saveAndFlush(smsCampaign);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.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());
|
||||
}
|
||||
}
|
||||
}
|
||||
return new CommandProcessingResultBuilder().withEntityId(smsCampaign.getId()).build();
|
||||
}
|
||||
|
||||
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause) {
|
||||
@@ -672,23 +572,4 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP
|
||||
}
|
||||
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.domain.AuditorAwareImpl;
|
||||
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.database.DatabaseTypeResolver;
|
||||
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.JpaBaseConfiguration;
|
||||
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.orm.jpa.EntityManagerFactoryBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -102,14 +100,6 @@ public class JPAConfig extends JpaBaseConfiguration {
|
||||
return new AuditorAwareImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager(ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
|
||||
ExtendedJpaTransactionManager transactionManager = new ExtendedJpaTransactionManager();
|
||||
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
|
||||
return transactionManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TransactionTemplate txTemplate(PlatformTransactionManager transactionManager) {
|
||||
TransactionTemplate tt = new TransactionTemplate();
|
||||
|
||||
+2
@@ -22,6 +22,7 @@ import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import javax.sql.DataSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.jdbc.datasource.AbstractDataSource;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -36,6 +37,7 @@ import org.springframework.stereotype.Service;
|
||||
* of this request.
|
||||
*/
|
||||
@Service(value = "routingDataSource")
|
||||
@Primary
|
||||
public class RoutingDataSource extends AbstractDataSource {
|
||||
|
||||
@Autowired
|
||||
|
||||
+13
-30
@@ -20,10 +20,10 @@ package org.apache.fineract.infrastructure.gcm.service;
|
||||
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.configuration.service.ExternalServicesPropertiesReadPlatformService;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
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.SmsMessageRepository;
|
||||
import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class NotificationSenderService {
|
||||
|
||||
private final DeviceRegistrationRepositoryWrapper deviceRegistrationRepositoryWrapper;
|
||||
private final SmsMessageRepository smsMessageRepository;
|
||||
private ExternalServicesPropertiesReadPlatformService propertiesReadPlatformService;
|
||||
|
||||
@Autowired
|
||||
public NotificationSenderService(final DeviceRegistrationRepositoryWrapper deviceRegistrationRepositoryWrapper,
|
||||
final SmsMessageRepository smsMessageRepository,
|
||||
final ExternalServicesPropertiesReadPlatformService propertiesReadPlatformService) {
|
||||
this.deviceRegistrationRepositoryWrapper = deviceRegistrationRepositoryWrapper;
|
||||
this.smsMessageRepository = smsMessageRepository;
|
||||
this.propertiesReadPlatformService = propertiesReadPlatformService;
|
||||
}
|
||||
private final ExternalServicesPropertiesReadPlatformService propertiesReadPlatformService;
|
||||
|
||||
public void sendNotification(List<SmsMessage> smsMessages) {
|
||||
Map<Long, List<SmsMessage>> notificationByEachClient = getNotificationListByClient(smsMessages);
|
||||
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)) {
|
||||
notificationByEachClient.get(clientId).add(smsMessage);
|
||||
} else {
|
||||
List<SmsMessage> msgList = new ArrayList<>(Arrays.asList(smsMessage));
|
||||
List<SmsMessage> msgList = new ArrayList<>(List.of(smsMessage));
|
||||
notificationByEachClient.put(clientId, msgList);
|
||||
}
|
||||
|
||||
@@ -81,10 +72,10 @@ public class NotificationSenderService {
|
||||
return notificationByEachClient;
|
||||
}
|
||||
|
||||
public void sendNotifiaction(Long clientId, List<SmsMessage> smsList) {
|
||||
public void sendNotification(Long clientId, List<SmsMessage> smsList) {
|
||||
|
||||
DeviceRegistration deviceRegistration = this.deviceRegistrationRepositoryWrapper.findDeviceRegistrationByClientId(clientId);
|
||||
NotificationConfigurationData notificationConfigurationData = this.propertiesReadPlatformService.getNotificationConfiguration();
|
||||
DeviceRegistration deviceRegistration = deviceRegistrationRepositoryWrapper.findDeviceRegistrationByClientId(clientId);
|
||||
NotificationConfigurationData notificationConfigurationData = propertiesReadPlatformService.getNotificationConfiguration();
|
||||
String registrationId = null;
|
||||
if (deviceRegistration != null) {
|
||||
registrationId = deviceRegistration.getRegistrationId();
|
||||
@@ -93,18 +84,10 @@ public class NotificationSenderService {
|
||||
try {
|
||||
Notification notification = new Notification.Builder(GcmConstants.defaultIcon).title(GcmConstants.title)
|
||||
.body(smsMessage.getMessage()).build();
|
||||
Message.Builder b = new Message.Builder();
|
||||
b.notification(notification);
|
||||
b.dryRun(false);
|
||||
b.contentAvailable(true);
|
||||
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);
|
||||
Message message = new Message.Builder().notification(notification).dryRun(false).contentAvailable(true)
|
||||
.timeToLive(GcmConstants.TIME_TO_LIVE).priority(Priority.HIGH).delayWhileIdle(true).build();
|
||||
Sender sender = new Sender(notificationConfigurationData.getServerKey(), notificationConfigurationData.getFcmEndPoint());
|
||||
Result res = sender.send(message, registrationId, 3);
|
||||
if (res.getSuccess() != null && res.getSuccess() > 0) {
|
||||
smsMessage.setStatusType(SmsMessageStatusType.SENT.getValue());
|
||||
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;
|
||||
|
||||
import java.util.Date;
|
||||
import lombok.Getter;
|
||||
|
||||
public class JobDetailData {
|
||||
|
||||
@@ -34,7 +35,7 @@ public class JobDetailData {
|
||||
@SuppressWarnings("unused")
|
||||
private final String initializingError;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Getter
|
||||
private final String cronExpression;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
||||
+40
-30
@@ -18,38 +18,43 @@
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.jobs.service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
public enum JobName {
|
||||
|
||||
UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), APPLY_ANNUAL_FEE_FOR_SAVINGS(
|
||||
"Apply Annual Fee For Savings"), APPLY_HOLIDAYS_TO_LOANS("Apply Holidays To Loans"), POST_INTEREST_FOR_SAVINGS(
|
||||
"Post Interest For Savings"), TRANSFER_FEE_CHARGE_FOR_LOANS(
|
||||
"Transfer Fee For Loans From Savings"), ACCOUNTING_RUNNING_BALANCE_UPDATE(
|
||||
"Update Accounting Running Balances"), PAY_DUE_SAVINGS_CHARGES(
|
||||
"Pay Due Savings Charges"), APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT(
|
||||
"Apply penalty to overdue loans"), EXECUTE_STANDING_INSTRUCTIONS(
|
||||
"Execute Standing Instruction"), ADD_ACCRUAL_ENTRIES(
|
||||
"Add Accrual Transactions"), UPDATE_NPA(
|
||||
"Update Non Performing Assets"), UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS(
|
||||
"Update Deposit Accounts Maturity details"), TRANSFER_INTEREST_TO_SAVINGS(
|
||||
"Transfer Interest To Savings"), ADD_PERIODIC_ACCRUAL_ENTRIES(
|
||||
"Add Periodic Accrual Transactions"), RECALCULATE_INTEREST_FOR_LOAN(
|
||||
"Recalculate Interest For Loans"), GENERATE_RD_SCEHDULE(
|
||||
"Generate Mandatory Savings Schedule"), GENERATE_LOANLOSS_PROVISIONING(
|
||||
"Generate Loan Loss Provisioning"), POST_DIVIDENTS_FOR_SHARES(
|
||||
"Post Dividends For Shares"), UPDATE_SAVINGS_DORMANT_ACCOUNTS(
|
||||
"Update Savings Dormant Accounts"), ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS(
|
||||
"Add Accrual Transactions For Loans With Income Posted As Transactions"), EXECUTE_REPORT_MAILING_JOBS(
|
||||
"Execute Report Mailing Jobs"), UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE(
|
||||
"Update SMS Outbound with Campaign Message"), SEND_MESSAGES_TO_SMS_GATEWAY(
|
||||
"Send Messages to SMS Gateway"), GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY(
|
||||
"Get Delivery Reports from SMS Gateway"), GENERATE_ADHOCCLIENT_SCEHDULE(
|
||||
"Generate AdhocClient Schedule"), UPDATE_EMAIL_OUTBOUND_WITH_CAMPAIGN_MESSAGE(
|
||||
"Update Email Outbound with campaign message"), EXECUTE_EMAIL(
|
||||
"Execute Email"), UPDATE_TRIAL_BALANCE_DETAILS(
|
||||
"Update Trial Balance Details"), 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");
|
||||
UPDATE_LOAN_ARREARS_AGEING("Update Loan Arrears Ageing"), //
|
||||
APPLY_ANNUAL_FEE_FOR_SAVINGS("Apply Annual Fee For Savings"), //
|
||||
APPLY_HOLIDAYS_TO_LOANS("Apply Holidays To Loans"), //
|
||||
POST_INTEREST_FOR_SAVINGS("Post Interest For Savings"), //
|
||||
TRANSFER_FEE_CHARGE_FOR_LOANS("Transfer Fee For Loans From Savings"), //
|
||||
ACCOUNTING_RUNNING_BALANCE_UPDATE("Update Accounting Running Balances"), //
|
||||
PAY_DUE_SAVINGS_CHARGES("Pay Due Savings Charges"), //
|
||||
APPLY_CHARGE_TO_OVERDUE_LOAN_INSTALLMENT("Apply penalty to overdue loans"), //
|
||||
EXECUTE_STANDING_INSTRUCTIONS("Execute Standing Instruction"), //
|
||||
ADD_ACCRUAL_ENTRIES("Add Accrual Transactions"), //
|
||||
UPDATE_NPA("Update Non Performing Assets"), //
|
||||
UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS("Update Deposit Accounts Maturity details"), //
|
||||
TRANSFER_INTEREST_TO_SAVINGS("Transfer Interest To Savings"), //
|
||||
ADD_PERIODIC_ACCRUAL_ENTRIES("Add Periodic Accrual Transactions"), //
|
||||
RECALCULATE_INTEREST_FOR_LOAN("Recalculate Interest For Loans"), //
|
||||
GENERATE_RD_SCEHDULE("Generate Mandatory Savings Schedule"), //
|
||||
GENERATE_LOANLOSS_PROVISIONING("Generate Loan Loss Provisioning"), //
|
||||
POST_DIVIDENTS_FOR_SHARES("Post Dividends For Shares"), //
|
||||
UPDATE_SAVINGS_DORMANT_ACCOUNTS("Update Savings Dormant Accounts"), //
|
||||
ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS(
|
||||
"Add Accrual Transactions For Loans With Income Posted As Transactions"), //
|
||||
EXECUTE_REPORT_MAILING_JOBS("Execute Report Mailing Jobs"), //
|
||||
UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE("Update SMS Outbound with Campaign Message"), //
|
||||
SEND_MESSAGES_TO_SMS_GATEWAY("Send Messages to SMS Gateway"), //
|
||||
GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY("Get Delivery Reports from SMS Gateway"), //
|
||||
GENERATE_ADHOC_CLIENT_SCHEDULE("Generate AdhocClient Schedule"), //
|
||||
UPDATE_EMAIL_OUTBOUND_WITH_CAMPAIGN_MESSAGE("Update Email Outbound with campaign message"), //
|
||||
EXECUTE_EMAIL("Execute Email"), //
|
||||
UPDATE_TRIAL_BALANCE_DETAILS("Update Trial Balance Details"), //
|
||||
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;
|
||||
|
||||
@@ -57,6 +62,11 @@ public enum JobName {
|
||||
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
|
||||
public String toString() {
|
||||
return this.name;
|
||||
|
||||
+4
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.jobs.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
|
||||
|
||||
public interface JobRegisterService {
|
||||
|
||||
void executeJob(Long jobId);
|
||||
@@ -32,6 +34,8 @@ public interface JobRegisterService {
|
||||
|
||||
void stopScheduler(String name);
|
||||
|
||||
void scheduleJob(ScheduledJobDetail scheduledJobDetails);
|
||||
|
||||
void stopAllSchedulers();
|
||||
|
||||
}
|
||||
|
||||
+47
-98
@@ -20,27 +20,22 @@ package org.apache.fineract.infrastructure.jobs.service;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
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.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.infrastructure.jobs.annotation.CronMethodParser;
|
||||
import org.apache.fineract.infrastructure.jobs.annotation.CronMethodParser.ClassMethodNamesPair;
|
||||
import org.apache.fineract.infrastructure.jobs.domain.JobParameter;
|
||||
import org.apache.fineract.infrastructure.jobs.domain.JobParameterRepository;
|
||||
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
|
||||
import org.apache.fineract.infrastructure.jobs.domain.SchedulerDetail;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobNodeIdMismatchingException;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
|
||||
import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobKey;
|
||||
@@ -49,10 +44,13 @@ import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.Trigger;
|
||||
import org.quartz.TriggerListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.batch.core.Job;
|
||||
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.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
|
||||
@@ -65,19 +63,12 @@ import org.springframework.stereotype.Service;
|
||||
* ,{@link MethodInvokingJobDetailFactoryBean} and {@link CronTriggerFactoryBean}
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class JobRegisterServiceImpl implements JobRegisterService, ApplicationListener<ContextClosedEvent> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JobRegisterServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private SchedularWritePlatformService schedularWritePlatformService;
|
||||
|
||||
@Autowired
|
||||
private TenantDetailsService tenantDetailsService;
|
||||
|
||||
@Autowired
|
||||
private SchedulerJobListener schedulerJobListener;
|
||||
|
||||
@@ -87,6 +78,9 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
||||
@Autowired
|
||||
private JobParameterRepository jobParameterRepository;
|
||||
|
||||
@Autowired
|
||||
private JobExplorer jobExplorer;
|
||||
|
||||
private final HashMap<String, Scheduler> schedulers = new HashMap<>(4);
|
||||
|
||||
// This cannot be injected as Autowired due to circular dependency
|
||||
@@ -95,29 +89,13 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
||||
@Autowired
|
||||
private FineractProperties fineractProperties;
|
||||
|
||||
@PostConstruct
|
||||
public void loadAllJobs() {
|
||||
// If the instance is not Batch Enabled will not load the Jobs
|
||||
if (!fineractProperties.getMode().isBatchManagerEnabled()) {
|
||||
return;
|
||||
}
|
||||
final List<FineractPlatformTenant> allTenants = this.tenantDetailsService.findAllTenants();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Autowired
|
||||
private JobLocator jobLocator;
|
||||
|
||||
private static final String JOB_LAUNCHER_STARTER_METHOD_NAME = "run";
|
||||
|
||||
public void executeJob(final ScheduledJobDetail scheduledJobDetail, String triggerType) {
|
||||
try {
|
||||
@@ -127,25 +105,24 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
||||
}
|
||||
jobDataMap.put(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE, triggerType);
|
||||
jobDataMap.put(SchedulerServiceConstants.TENANT_IDENTIFIER, ThreadLocalContextUtil.getTenant().getTenantIdentifier());
|
||||
final String key = scheduledJobDetail.getJobKey();
|
||||
final JobKey jobKey = constructJobKey(key);
|
||||
final String schedulerName = getSchedulerName(scheduledJobDetail);
|
||||
final Scheduler scheduler = this.schedulers.get(schedulerName);
|
||||
final JobDetail jobDetail = createJobDetail(scheduledJobDetail);
|
||||
JobKey jobKey = jobDetail.getKey();
|
||||
if (scheduler == null || !scheduler.checkExists(jobKey)) {
|
||||
final JobDetail jobDetail = createJobDetail(scheduledJobDetail);
|
||||
final String tempSchedulerName = "temp" + scheduledJobDetail.getId();
|
||||
final Scheduler tempScheduler = createScheduler(tempSchedulerName, 1, schedulerJobListener, schedulerStopListener);
|
||||
tempScheduler.addJob(jobDetail, true);
|
||||
jobDataMap.put(SchedulerServiceConstants.SCHEDULER_NAME, tempSchedulerName);
|
||||
this.schedulers.put(tempSchedulerName, tempScheduler);
|
||||
tempScheduler.triggerJob(jobDetail.getKey(), jobDataMap);
|
||||
schedulers.put(tempSchedulerName, tempScheduler);
|
||||
tempScheduler.addJob(jobDetail, true);
|
||||
tempScheduler.triggerJob(jobKey, jobDataMap);
|
||||
} else {
|
||||
scheduler.addJob(jobDetail, true);
|
||||
scheduler.triggerJob(jobKey, jobDataMap);
|
||||
}
|
||||
|
||||
} catch (final Exception e) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -206,7 +183,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
||||
}
|
||||
}
|
||||
} catch (final SchedulerException e) {
|
||||
LOG.error("Error occured.", e);
|
||||
log.error("Error occured.", e);
|
||||
}
|
||||
}
|
||||
jobDetail.updateTriggerMisfired(false);
|
||||
@@ -264,7 +241,8 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
||||
this.stopAllSchedulers();
|
||||
}
|
||||
|
||||
private void scheduleJob(final ScheduledJobDetail scheduledJobDetails) {
|
||||
@Override
|
||||
public void scheduleJob(final ScheduledJobDetail scheduledJobDetails) {
|
||||
if (!scheduledJobDetails.isActiveSchedular()) {
|
||||
scheduledJobDetails.updateNextRunTime(null);
|
||||
scheduledJobDetails.updateCurrentlyRunningStatus(false);
|
||||
@@ -282,7 +260,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
||||
scheduledJobDetails.updateNextRunTime(null);
|
||||
final String stackTrace = getStackTraceAsString(throwable);
|
||||
scheduledJobDetails.updateErrorLog(stackTrace);
|
||||
LOG.error("Could not schedule job: {}", scheduledJobDetails.getJobName(), throwable);
|
||||
log.error("Could not schedule job: {}", scheduledJobDetails.getJobName(), throwable);
|
||||
}
|
||||
scheduledJobDetails.updateCurrentlyRunningStatus(false);
|
||||
}
|
||||
@@ -293,7 +271,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
|
||||
try {
|
||||
scheduler.shutdown();
|
||||
} 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 {
|
||||
scheduler.shutdown();
|
||||
} 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 {
|
||||
final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
|
||||
final ClassMethodNamesPair jobDetails = CronMethodParser.findTargetMethodDetails(scheduledJobDetail.getJobName());
|
||||
if (jobDetails == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"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));
|
||||
|
||||
JobName jobName = JobName.getJobByName(scheduledJobDetail.getJobName());
|
||||
Job job = jobLocator.getJob(jobName.name());
|
||||
|
||||
final MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean();
|
||||
jobDetailFactoryBean.setName(scheduledJobDetail.getJobName() + "JobDetail" + tenant.getId());
|
||||
jobDetailFactoryBean.setTargetObject(targetObject);
|
||||
jobDetailFactoryBean.setTargetMethod(jobDetails.methodName);
|
||||
jobDetailFactoryBean.setTargetObject(jobLauncher);
|
||||
jobDetailFactoryBean.setTargetMethod(JOB_LAUNCHER_STARTER_METHOD_NAME);
|
||||
jobDetailFactoryBean.setGroup(scheduledJobDetail.getGroupName());
|
||||
jobDetailFactoryBean.setConcurrent(false);
|
||||
Map<String, String> jobParameterMap = getJobParameter(scheduledJobDetail);
|
||||
if (!jobParameterMap.isEmpty()) {
|
||||
jobDetailFactoryBean.setArguments(jobParameterMap);
|
||||
}
|
||||
|
||||
Map<String, org.springframework.batch.core.JobParameter> jobParameterMap = getJobParameter(scheduledJobDetail);
|
||||
JobParameters jobParameters = new JobParametersBuilder(jobExplorer).getNextJobParameters(job)
|
||||
.addJobParameters(new JobParameters(jobParameterMap)).toJobParameters();
|
||||
|
||||
jobDetailFactoryBean.setArguments(job, jobParameters);
|
||||
jobDetailFactoryBean.afterPropertiesSet();
|
||||
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());
|
||||
Map<String, String> jobParameterMap = new HashMap<>();
|
||||
Map<String, org.springframework.batch.core.JobParameter> jobParameterMap = new HashMap<>();
|
||||
for (JobParameter jobparameter : jobParameterList) {
|
||||
jobParameterMap.put(jobparameter.getParameterName(), jobparameter.getParameterValue());
|
||||
jobParameterMap.put(jobparameter.getParameterName(),
|
||||
new org.springframework.batch.core.JobParameter(jobparameter.getParameterValue()));
|
||||
}
|
||||
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 {
|
||||
final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
|
||||
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 retrieveOneByName(String jobName);
|
||||
|
||||
Page<JobDetailHistoryData> retrieveJobHistory(Long jobId, SearchParameters searchParameters);
|
||||
|
||||
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
|
||||
public Page<JobDetailHistoryData> retrieveJobHistory(final Long jobId, final SearchParameters searchParameters) {
|
||||
if (!isJobExist(jobId)) {
|
||||
@@ -142,9 +153,9 @@ public class SchedulerJobRunnerReadServiceImpl implements SchedulerJobRunnerRead
|
||||
JobDetailMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
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,")
|
||||
.append(" runHistory.version,runHistory.start_time as lastRunStartTime,runHistory.end_time as lastRunEndTime,runHistory."
|
||||
+ sqlGenerator.escape("status")
|
||||
+ ",runHistory.error_message as jobRunErrorMessage,runHistory.trigger_type as triggerType,runHistory.error_log as jobRunErrorLog ")
|
||||
.append(" runHistory.version,runHistory.start_time as lastRunStartTime,runHistory.end_time as lastRunEndTime,runHistory.")
|
||||
.append(sqlGenerator.escape("status"))
|
||||
.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 ");
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -36,6 +36,8 @@ import org.quartz.TriggerListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@@ -62,6 +64,7 @@ public class SchedulerTriggerListener implements TriggerListener {
|
||||
@Override
|
||||
@SuppressFBWarnings(value = {
|
||||
"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) {
|
||||
final String tenantIdentifier = trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
|
||||
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
|
||||
* 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.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||
|
||||
public interface SavingsSchedularService {
|
||||
|
||||
void postInterestForAccounts(Map<String, String> jobParameters) throws JobExecutionException;
|
||||
|
||||
void updateSavingsDormancyStatus() throws JobExecutionException;
|
||||
public interface IncreaseDateBy1DayService {
|
||||
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
|
||||
public interface ReportMailingJobWritePlatformService {
|
||||
|
||||
@@ -29,6 +28,4 @@ public interface ReportMailingJobWritePlatformService {
|
||||
CommandProcessingResult updateReportMailingJob(Long reportMailingJobId, JsonCommand jsonCommand);
|
||||
|
||||
CommandProcessingResult deleteReportMailingJob(Long reportMailingJobId);
|
||||
|
||||
void executeReportMailingJobs() throws JobExecutionException;
|
||||
}
|
||||
|
||||
+1
-256
@@ -18,18 +18,9 @@
|
||||
*/
|
||||
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.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
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.fineract.infrastructure.core.api.JsonCommand;
|
||||
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.dataqueries.domain.Report;
|
||||
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.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.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.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -79,29 +55,17 @@ public class ReportMailingJobWritePlatformServiceImpl implements ReportMailingJo
|
||||
private final ReportMailingJobRepositoryWrapper reportMailingJobRepositoryWrapper;
|
||||
private final ReportMailingJobRepository reportMailingJobRepository;
|
||||
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
|
||||
public ReportMailingJobWritePlatformServiceImpl(final ReportRepositoryWrapper reportRepositoryWrapper,
|
||||
final ReportMailingJobValidator reportMailingJobValidator,
|
||||
final ReportMailingJobRepositoryWrapper reportMailingJobRepositoryWrapper,
|
||||
final ReportMailingJobRepository reportMailingJobRepository, final PlatformSecurityContext platformSecurityContext,
|
||||
final ReportMailingJobEmailService reportMailingJobEmailService, final ReadReportingService readReportingService,
|
||||
final ReportMailingJobRunHistoryRepository reportMailingJobRunHistoryRepository,
|
||||
final ReportingProcessServiceProvider reportingProcessServiceProvider) {
|
||||
final PlatformSecurityContext platformSecurityContext) {
|
||||
this.reportRepositoryWrapper = reportRepositoryWrapper;
|
||||
this.reportMailingJobValidator = reportMailingJobValidator;
|
||||
this.reportMailingJobRepositoryWrapper = reportMailingJobRepositoryWrapper;
|
||||
this.reportMailingJobRepository = reportMailingJobRepositoryWrapper.getReportMailingJobRepository();
|
||||
this.platformSecurityContext = platformSecurityContext;
|
||||
this.reportMailingJobEmailService = reportMailingJobEmailService;
|
||||
this.readReportingService = readReportingService;
|
||||
this.reportMailingJobRunHistoryRepository = reportMailingJobRunHistoryRepository;
|
||||
this.reportingProcessServiceProvider = reportingProcessServiceProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -242,114 +206,6 @@ public class ReportMailingJobWritePlatformServiceImpl implements ReportMailingJo
|
||||
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
|
||||
*
|
||||
@@ -369,29 +225,6 @@ public class ReportMailingJobWritePlatformServiceImpl implements ReportMailingJo
|
||||
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
|
||||
*
|
||||
@@ -415,92 +248,4 @@ public class ReportMailingJobWritePlatformServiceImpl implements ReportMailingJo
|
||||
throw new PlatformDataIntegrityException("error.msg.charge.unknown.data.integrity.issue",
|
||||
"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.MediaType;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.accounting.journalentry.api.DateParam;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
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.sms.data.SmsData;
|
||||
import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -56,6 +56,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Tag(name = "SMS", description = "")
|
||||
@RequiredArgsConstructor
|
||||
public class SmsApiResource {
|
||||
|
||||
private final String resourceNameForPermissions = "SMS";
|
||||
@@ -66,17 +67,6 @@ public class SmsApiResource {
|
||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||
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
|
||||
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("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);
|
||||
|
||||
@@ -130,11 +120,11 @@ public class SmsApiResource {
|
||||
toDate = toDateParam.getDate("toDate", dateFormat, locale);
|
||||
}
|
||||
|
||||
final Page<SmsData> smsMessages = this.readPlatformService.retrieveSmsByStatus(campaignId, searchParameters, status.intValue(),
|
||||
fromDate, toDate);
|
||||
final Page<SmsData> smsMessages = readPlatformService.retrieveSmsByStatus(campaignId, searchParameters, status.intValue(), fromDate,
|
||||
toDate);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, smsMessages);
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return toApiJsonSerializer.serialize(settings, smsMessages);
|
||||
}
|
||||
|
||||
@PUT
|
||||
|
||||
-10
@@ -28,11 +28,6 @@ import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
|
||||
**/
|
||||
public interface SmsMessageScheduledJobService {
|
||||
|
||||
/**
|
||||
* sends a batch of SMS messages to the SMS gateway
|
||||
**/
|
||||
void sendMessagesToGateway();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -28,34 +27,27 @@ 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.fineract.infrastructure.campaigns.helper.SmsConfigUtils;
|
||||
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.exception.ConnectionFailureException;
|
||||
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.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.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.beans.factory.annotation.Autowired;
|
||||
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.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
@@ -63,89 +55,22 @@ import org.springframework.web.client.RestTemplate;
|
||||
**/
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJobService {
|
||||
|
||||
private final SmsMessageRepository smsMessageRepository;
|
||||
private final SmsReadPlatformService smsReadPlatformService;
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private final SmsConfigUtils smsConfigUtils;
|
||||
private final NotificationSenderService notificationSenderService;
|
||||
private ExecutorService genericExecutorService;
|
||||
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
|
||||
public void initializeExecutorService() {
|
||||
genericExecutorService = 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) {
|
||||
Map<String, Object> hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("sms",
|
||||
SmsMessageApiQueueResourceData.toJsonString(apiQueueResourceDatas));
|
||||
@@ -172,7 +97,6 @@ public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJob
|
||||
for (Map.Entry<SmsCampaign, Collection<SmsMessage>> entry : smsDataMap.entrySet()) {
|
||||
Iterator<SmsMessage> smsMessageIterator = entry.getValue().iterator();
|
||||
Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas = new ArrayList<>();
|
||||
StringBuilder request = new StringBuilder();
|
||||
while (smsMessageIterator.hasNext()) {
|
||||
SmsMessage smsMessage = smsMessageIterator.next();
|
||||
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> {
|
||||
|
||||
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 sql = "select id from " + this.smsRowMapper.tableName() + " where status_enum = "
|
||||
+ SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue() + sqlPlusLimit;
|
||||
return this.paginationHelper.fetchPage(jdbcTemplate, sql, Long.class);
|
||||
// (this.jdbcTemplate, sqlCountRows, new Object [] {}, Long.class);
|
||||
// this.jdbcTemplate.queryForList(sql, Long.class);
|
||||
return paginationHelper.fetchPage(jdbcTemplate, sql, Long.class);
|
||||
}
|
||||
|
||||
@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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
|
||||
public interface StandingInstructionWritePlatformService {
|
||||
|
||||
@@ -28,8 +27,6 @@ public interface StandingInstructionWritePlatformService {
|
||||
|
||||
CommandProcessingResult update(Long id, JsonCommand command);
|
||||
|
||||
void executeStandingInstructions() throws JobExecutionException;
|
||||
|
||||
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.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.List;
|
||||
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.data.CommandProcessingResult;
|
||||
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.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.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.StandingInstructionDuesData;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
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.StandingInstructionAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
|
||||
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.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.NonTransientDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class StandingInstructionWritePlatformServiceImpl implements StandingInstructionWritePlatformService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StandingInstructionWritePlatformServiceImpl.class);
|
||||
|
||||
private final StandingInstructionDataValidator standingInstructionDataValidator;
|
||||
private final StandingInstructionAssembler standingInstructionAssembler;
|
||||
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
||||
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
|
||||
@Override
|
||||
@@ -152,7 +106,7 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst
|
||||
throw new PlatformDataIntegrityException("error.msg.standinginstruction.duplicate.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",
|
||||
"Unknown data integrity issue with resource.");
|
||||
}
|
||||
@@ -190,110 +144,4 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst
|
||||
actualChanges.put(statusParamName, StandingInstructionStatus.DELETED.getValue());
|
||||
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
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.adhocquery.service;
|
||||
package org.apache.fineract.portfolio.loanaccount.jobs.generateloanlossprovisioning;
|
||||
|
||||
public interface AdHocScheduledJobRunnerService {
|
||||
|
||||
void generateClientSchedule();
|
||||
}
|
||||
public class NoProvisioningCriteriaDefinitionFoundException extends RuntimeException {}
|
||||
+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.util.Collection;
|
||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
|
||||
public interface LoanAccrualPlatformService {
|
||||
@@ -30,10 +29,4 @@ public interface LoanAccrualPlatformService {
|
||||
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LoanAccrualPlatformServiceImpl.class);
|
||||
|
||||
private final LoanReadPlatformService loanReadPlatformService;
|
||||
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
|
||||
public void addPeriodicAccruals(final LocalDate tilldate) throws JobExecutionException {
|
||||
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = this.loanReadPlatformService.retrivePeriodicAccrualData(tilldate);
|
||||
@@ -114,7 +63,7 @@ public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformServic
|
||||
try {
|
||||
this.loanAccrualWritePlatformService.addPeriodicAccruals(tilldate, mapEntry.getKey(), mapEntry.getValue());
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
@@ -122,24 +71,4 @@ public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformServic
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
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.loanproduct.service.LoanEnumerations;
|
||||
import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlatformService {
|
||||
|
||||
private final LoanReadPlatformService loanReadPlatformService;
|
||||
@@ -65,22 +66,6 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
|
||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||
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
|
||||
@Transactional
|
||||
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,
|
||||
final List<Long> existingReversedTransactionIds) {
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
final ApplicationCurrency applicationCurrency = applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
boolean isAccountTransfer = false;
|
||||
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
|
||||
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
||||
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-2
@@ -18,14 +18,21 @@
|
||||
*/
|
||||
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.loanschedule.data.LoanSchedulePeriodData;
|
||||
|
||||
public interface LoanArrearsAgingService {
|
||||
|
||||
void updateLoanArrearsAgeingDetails();
|
||||
|
||||
void updateLoanArrearsAgeingDetailsWithOriginalSchedule(Loan loan);
|
||||
|
||||
Map<Long, List<LoanSchedulePeriodData>> getScheduleDate(String loanId);
|
||||
|
||||
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 org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
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.jobs.annotation.CronTarget;
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.portfolio.businessevent.BusinessEventListener;
|
||||
import org.apache.fineract.portfolio.businessevent.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||
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.ResultSetExtractor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@@ -92,54 +88,6 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
||||
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
|
||||
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 =?",
|
||||
@@ -150,7 +98,7 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
||||
originalScheduleExtractor);
|
||||
if (scheduleDate.size() > 0) {
|
||||
List<Map<String, Object>> transactions = getLoanSummary(loan.getId(), loan.getLoanSummary());
|
||||
updateSchheduleWithPaidDetail(scheduleDate, transactions);
|
||||
updateScheduleWithPaidDetail(scheduleDate, transactions);
|
||||
createInsertStatements(updateStatement, scheduleDate, count == 0);
|
||||
if (updateStatement.size() == 1) {
|
||||
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
|
||||
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 =?",
|
||||
@@ -208,50 +162,6 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
||||
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) {
|
||||
List<Map<String, Object>> transactionDetail = new ArrayList<>();
|
||||
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) {
|
||||
for (Map.Entry<Long, List<LoanSchedulePeriodData>> entry : scheduleDate.entrySet()) {
|
||||
final Long loanId = entry.getKey();
|
||||
@@ -341,25 +252,10 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
||||
return insertStatementBuilder.toString();
|
||||
}
|
||||
|
||||
private void updateSchheduleWithPaidDetail(Map<Long, List<LoanSchedulePeriodData>> scheduleDate,
|
||||
List<Map<String, Object>> loanSummary) {
|
||||
@Override
|
||||
public void updateScheduleWithPaidDetail(Map<Long, List<LoanSchedulePeriodData>> scheduleDate, List<Map<String, Object>> loanSummary) {
|
||||
for (Map<String, Object> transactionMap : loanSummary) {
|
||||
String longValue = transactionMap.get("loanId").toString(); // From
|
||||
// JDBC
|
||||
// Template
|
||||
// API,
|
||||
// we
|
||||
// are
|
||||
// getting
|
||||
// BigInteger
|
||||
// but
|
||||
// in
|
||||
// other
|
||||
// call,
|
||||
// we
|
||||
// are
|
||||
// getting
|
||||
// Long
|
||||
String longValue = transactionMap.get("loanId").toString();
|
||||
Long loanId = Long.parseLong(longValue);
|
||||
BigDecimal principalAmtPaid = (BigDecimal) transactionMap.get("principalAmtPaid");
|
||||
BigDecimal principalAmtWrittenoff = (BigDecimal) transactionMap.get("principalAmtWrittenoff");
|
||||
@@ -375,15 +271,15 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
||||
BigDecimal feeAmt = feeAmtPaid.add(feeAmtWaived);
|
||||
BigDecimal penaltyAmt = penaltyAmtPaid.add(penaltyAmtWaived);
|
||||
|
||||
List<LoanSchedulePeriodData> loanSchedulePeriodDatas = scheduleDate.get(loanId);
|
||||
if (loanSchedulePeriodDatas != null) {
|
||||
List<LoanSchedulePeriodData> updatedPeriodData = new ArrayList<>(loanSchedulePeriodDatas.size());
|
||||
for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDatas) {
|
||||
BigDecimal principalPaid = null;
|
||||
BigDecimal interestPaid = null;
|
||||
BigDecimal feeChargesPaid = null;
|
||||
BigDecimal penaltyChargesPaid = null;
|
||||
Boolean isComplete = true;
|
||||
List<LoanSchedulePeriodData> loanSchedulePeriodDataList = scheduleDate.get(loanId);
|
||||
if (loanSchedulePeriodDataList != null) {
|
||||
List<LoanSchedulePeriodData> updatedPeriodData = new ArrayList<>(loanSchedulePeriodDataList.size());
|
||||
for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDataList) {
|
||||
BigDecimal principalPaid;
|
||||
BigDecimal interestPaid;
|
||||
BigDecimal feeChargesPaid;
|
||||
BigDecimal penaltyChargesPaid;
|
||||
boolean isComplete = true;
|
||||
if (loanSchedulePeriodData.principalDue().compareTo(principalAmt) > 0) {
|
||||
principalPaid = principalAmt;
|
||||
principalAmt = BigDecimal.ZERO;
|
||||
@@ -422,8 +318,8 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
||||
principalPaid, interestPaid, feeChargesPaid, penaltyChargesPaid);
|
||||
updatedPeriodData.add(periodData);
|
||||
}
|
||||
loanSchedulePeriodDatas.clear();
|
||||
loanSchedulePeriodDatas.addAll(updatedPeriodData);
|
||||
loanSchedulePeriodDataList.clear();
|
||||
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 org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
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.staff.domain.Staff;
|
||||
import org.apache.fineract.portfolio.calendar.domain.Calendar;
|
||||
@@ -78,8 +77,6 @@ public interface LoanWritePlatformService {
|
||||
void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances,
|
||||
Boolean reschedulebasedOnMeetingDates, LocalDate presentMeetingDate, LocalDate newMeetingDate);
|
||||
|
||||
void applyHolidaysToLoans();
|
||||
|
||||
LoanTransaction initiateLoanTransfer(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);
|
||||
|
||||
void transferFeeCharges() throws JobExecutionException;
|
||||
|
||||
CommandProcessingResult undoWriteOff(Long loanId);
|
||||
|
||||
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.StatusEnum;
|
||||
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.organisation.holiday.domain.Holiday;
|
||||
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.exception.CalendarParameterUpdateNotSupportedException;
|
||||
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.exception.ChargeCannotBeUpdatedException;
|
||||
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.command.LoanUpdateCommand;
|
||||
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.LoanInstallmentChargeData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||
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) {
|
||||
final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId)
|
||||
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
|
||||
@@ -2524,50 +2458,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
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) {
|
||||
final Client client = loan.client();
|
||||
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