FINERACT-2104: Accrual Activity Posting Job

This commit is contained in:
Soma Sörös
2024-07-18 12:57:38 +02:00
committed by Adam Saghy
parent 38d994452d
commit 905a733f9e
11 changed files with 430 additions and 29 deletions
@@ -19,18 +19,11 @@
package org.apache.fineract.cob.loan;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService;
import org.springframework.stereotype.Component;
@Slf4j
@@ -38,32 +31,15 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor
public class AccrualActivityPostingBusinessStep implements LoanCOBBusinessStep {
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanAccountDomainService loanAccountDomainService;
private final ExternalIdFactory externalIdFactory;
private final LoanWritePlatformService loanWritePlatformService;
private final LoanAccrualActivityProcessingService loanAccrualActivityProcessingService;
@Override
public Loan execute(Loan loan) {
log.debug("start processing loan accrual activity posting on installment due date with id [{}]", loan.getId());
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
// check if loan capable for posting
if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
// check if loan has installment due on business day
Optional<LoanRepaymentScheduleInstallment> first = loan.getRepaymentScheduleInstallments().stream()
.filter(loanRepaymentScheduleInstallment -> loanRepaymentScheduleInstallment.getDueDate().isEqual(currentDate))
.findFirst();
if (first.isPresent()) {
final LoanRepaymentScheduleInstallment installment = first.get();
// check if there is no not-replayed-accrual-activity related to business date
List<LoanTransaction> loanTransactions = loan.getLoanTransactions(loanTransaction -> loanTransaction.isNotReversed()
&& loanTransaction.isAccrualActivity() && loanTransaction.getTransactionDate().isEqual(currentDate));
if (loanTransactions.isEmpty()) {
loan = loanWritePlatformService.makeAccrualActivityTransaction(loan, installment, currentDate);
}
}
}
loanAccrualActivityProcessingService.makeAccrualActivityTransaction(loan, currentDate);
log.debug("end processing loan accrual activity posting on installment due date with id [{}]", loan.getId());
return loan;
@@ -0,0 +1,51 @@
/**
* 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.accrualactivityposting;
import lombok.RequiredArgsConstructor;
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.job.builder.JobBuilder;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
@RequiredArgsConstructor
public class AccrualActivityPostingConfig {
private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
@Bean
protected Step accrualActivityPostingStep(AccrualActivityPostingTasklet accrualActivityPostingTasklet) {
return new StepBuilder(JobName.ACCRUAL_ACTIVITY_POSTING.name(), jobRepository)
.tasklet(accrualActivityPostingTasklet, transactionManager).build();
}
@Bean
public Job accrualActivityPostingJob(AccrualActivityPostingTasklet accrualActivityPosting) {
return new JobBuilder(JobName.ACCRUAL_ACTIVITY_POSTING.name(), jobRepository)
.start(accrualActivityPostingStep(accrualActivityPosting)).incrementer(new RunIdIncrementer()).build();
}
}
@@ -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.accrualactivityposting;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
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.loanaccount.domain.LoanAccrualActivityRepository;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService;
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 AccrualActivityPostingTasklet implements Tasklet {
private final LoanAccrualActivityProcessingService loanAccrualActivityProcessingService;
private final LoanAccrualActivityRepository loanAccrualActivityRepository;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
final LocalDate yesterday = DateUtils.getBusinessLocalDate().minusDays(1);
List<Throwable> errors = new ArrayList<>();
Set<Long> loanAccounts = loanAccrualActivityRepository.fetchLoanIdsForAccrualActivityPosting(yesterday);
for (Long accountId : loanAccounts) {
try {
loanAccrualActivityProcessingService.makeAccrualActivityTransaction(accountId, yesterday);
} catch (Exception e) {
log.error("Failed to add accrual activity transaction for loan {}", accountId, e);
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw new JobExecutionException(errors);
}
return RepeatStatus.FINISHED;
}
}
@@ -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.portfolio.loanaccount.service;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Component
@RequiredArgsConstructor
public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActivityProcessingService {
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final LoanWritePlatformService loanWritePlatformService;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void makeAccrualActivityTransaction(Long loanId, final LocalDate currentDate) {
Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
makeAccrualActivityTransaction(loan, currentDate);
}
@Override
public Loan makeAccrualActivityTransaction(Loan loan, final LocalDate currentDate) {
if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
// check if loan has installment due on business day
Optional<LoanRepaymentScheduleInstallment> first = loan.getRepaymentScheduleInstallments().stream()
.filter(loanRepaymentScheduleInstallment -> loanRepaymentScheduleInstallment.getDueDate().isEqual(currentDate))
.findFirst();
if (first.isPresent()) {
final LoanRepaymentScheduleInstallment installment = first.get();
// check if there is no not-replayed-accrual-activity related to business date
List<LoanTransaction> loanTransactions = loan.getLoanTransactions(loanTransaction -> loanTransaction.isNotReversed()
&& loanTransaction.isAccrualActivity() && loanTransaction.getTransactionDate().isEqual(currentDate));
if (loanTransactions.isEmpty()) {
loan = loanWritePlatformService.makeAccrualActivityTransaction(loan, installment, currentDate);
}
}
}
return loan;
}
}
@@ -162,4 +162,5 @@
<include file="parts/0140_trial_balance_with_asset_transfer_update.xml" relativeToChangelogFile="true" />
<include file="parts/0141_add_interest_payment_waiver_transaction_type.xml" relativeToChangelogFile="true" />
<include file="parts/0142_add_accrual_activity_transaction.xml" relativeToChangelogFile="true" />
<include file="parts/0143_add_accrual_activity_posting_job.xml" relativeToChangelogFile="true" />
</databaseChangeLog>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<insert tableName="job">
<column name="name" value="Accrual Activity Posting"/>
<column name="display_name" value="Accrual Activity Posting"/>
<column name="cron_expression" value="0 0 1 * * ?"/>
<column name="create_time" valueDate="${current_datetime}"/>
<column name="task_priority" valueNumeric="5"/>
<column name="group_name"/>
<column name="previous_run_start_time"/>
<column name="job_key" value="Accrual Activity Posting1 _ DEFAULT"/>
<column name="initializing_errorlog"/>
<column name="is_active" valueBoolean="false"/>
<column name="currently_running" valueBoolean="false"/>
<column name="updates_allowed" valueBoolean="true"/>
<column name="scheduler_group" valueNumeric="0"/>
<column name="is_misfired" valueBoolean="false"/>
<column name="node_id" valueNumeric="1"/>
<column name="is_mismatched_job" valueBoolean="true"/>
</insert>
</changeSet>
</databaseChangeLog>