blob: 5b5550f8106a646948babd50dcbb5fa5c23a65a6 [file] [log] [blame]
/**
* 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.domain.ExternalId;
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 (DateUtils.isBefore(startDate, data.validFrom())) {
startDate = startDate.plusMonths(1);
}
} else if (frequencyType.isYearly()) {
startDate = startDate.withDayOfMonth(data.recurrenceOnDay()).withMonth(data.recurrenceOnMonth());
if (DateUtils.isBefore(startDate, 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().getId());
if (data.instructionType().isDuesAmoutTransfer()) {
transactionAmount = standingInstructionDuesData.totalDueAmount();
}
if (recurrenceType.isDuesRecurrence()) {
isDueForTransfer = isDueForTransfer(standingInstructionDuesData);
}
}
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().getId(), data.toAccount().getId(),
data.name() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null,
data.transferType().getValue(), null, null, ExternalId.empty(), 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;
}
public boolean isDueForTransfer(StandingInstructionDuesData standingInstructionDuesData) {
return standingInstructionDuesData.dueDate() != null
&& !standingInstructionDuesData.dueDate().isAfter(LocalDate.now(DateUtils.getDateTimeZoneOfTenant()));
}
}