FINERACT-1760: Raise transaction replayed business event
This commit is contained in:
committed by
Arnold Gálovics
parent
bbd1ca75a1
commit
4c597b61fb
+19
-12
@@ -51,20 +51,27 @@ public class COBBusinessStepServiceImpl implements COBBusinessStepService {
|
|||||||
if (executionMap == null || executionMap.isEmpty()) {
|
if (executionMap == null || executionMap.isEmpty()) {
|
||||||
throw new BusinessStepException("Execution map is empty! COB Business step execution skipped!");
|
throw new BusinessStepException("Execution map is empty! COB Business step execution skipped!");
|
||||||
}
|
}
|
||||||
businessEventNotifierService.startExternalEventRecording();
|
// Extra safety net to avoid event leaking
|
||||||
for (String businessStep : executionMap.values()) {
|
try {
|
||||||
try {
|
businessEventNotifierService.startExternalEventRecording();
|
||||||
ThreadLocalContextUtil.setActionContext(ActionContext.COB);
|
|
||||||
COBBusinessStep<S> businessStepBean = (COBBusinessStep<S>) applicationContext.getBean(businessStep);
|
for (String businessStep : executionMap.values()) {
|
||||||
item = businessStepBean.execute(item);
|
try {
|
||||||
} catch (Exception e) {
|
ThreadLocalContextUtil.setActionContext(ActionContext.COB);
|
||||||
throw new BusinessStepException("Error happened during business step execution", e);
|
COBBusinessStep<S> businessStepBean = (COBBusinessStep<S>) applicationContext.getBean(businessStep);
|
||||||
} finally {
|
item = businessStepBean.execute(item);
|
||||||
// Fallback to COB action context after each business step
|
} catch (Exception e) {
|
||||||
ThreadLocalContextUtil.setActionContext(ActionContext.COB);
|
throw new BusinessStepException("Error happened during business step execution", e);
|
||||||
|
} finally {
|
||||||
|
// Fallback to COB action context after each business step
|
||||||
|
ThreadLocalContextUtil.setActionContext(ActionContext.COB);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
businessEventNotifierService.stopExternalEventRecording();
|
||||||
|
} catch (Exception e) {
|
||||||
|
businessEventNotifierService.resetEventRecording();
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
businessEventNotifierService.stopExternalEventRecording();
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
@@ -50,4 +50,6 @@ public interface BusinessEventNotifierService {
|
|||||||
void startExternalEventRecording();
|
void startExternalEventRecording();
|
||||||
|
|
||||||
void stopExternalEventRecording();
|
void stopExternalEventRecording();
|
||||||
|
|
||||||
|
void resetEventRecording();
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -155,4 +155,10 @@ public class BusinessEventNotifierServiceImpl implements BusinessEventNotifierSe
|
|||||||
recordedEvents.remove();
|
recordedEvents.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetEventRecording() {
|
||||||
|
eventRecordingEnabled.set(false);
|
||||||
|
recordedEvents.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -91,6 +91,7 @@ import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService;
|
||||||
import org.apache.fineract.portfolio.note.domain.Note;
|
import org.apache.fineract.portfolio.note.domain.Note;
|
||||||
import org.apache.fineract.portfolio.note.domain.NoteRepository;
|
import org.apache.fineract.portfolio.note.domain.NoteRepository;
|
||||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||||
@@ -127,6 +128,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
private final DelinquencyWritePlatformService delinquencyWritePlatformService;
|
private final DelinquencyWritePlatformService delinquencyWritePlatformService;
|
||||||
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
|
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
|
||||||
private final ExternalIdFactory externalIdFactory;
|
private final ExternalIdFactory externalIdFactory;
|
||||||
|
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -210,11 +212,14 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
|
|
||||||
if (changedTransactionDetail != null) {
|
if (changedTransactionDetail != null) {
|
||||||
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||||
|
|
||||||
saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||||
// update loan with references to the newly created transactions
|
// update loan with references to the newly created transactions
|
||||||
loan.addLoanTransaction(mapEntry.getValue());
|
loan.addLoanTransaction(mapEntry.getValue());
|
||||||
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(noteText)) {
|
if (StringUtils.isNotBlank(noteText)) {
|
||||||
@@ -788,6 +793,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
loan.getLoanTransactions().add(mapEntry.getValue());
|
loan.getLoanTransactions().add(mapEntry.getValue());
|
||||||
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
|
|||||||
+6
-40
@@ -30,6 +30,7 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||||
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
||||||
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
|
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
|
||||||
@@ -78,10 +79,10 @@ import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanResch
|
|||||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
|
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.dao.NonTransientDataAccessException;
|
import org.springframework.dao.NonTransientDataAccessException;
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
@@ -89,6 +90,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanRescheduleRequestWritePlatformService {
|
public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanRescheduleRequestWritePlatformService {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(LoanRescheduleRequestWritePlatformServiceImpl.class);
|
private static final Logger LOG = LoggerFactory.getLogger(LoanRescheduleRequestWritePlatformServiceImpl.class);
|
||||||
@@ -111,45 +113,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
|||||||
private static final DefaultScheduledDateGenerator DEFAULT_SCHEDULED_DATE_GENERATOR = new DefaultScheduledDateGenerator();
|
private static final DefaultScheduledDateGenerator DEFAULT_SCHEDULED_DATE_GENERATOR = new DefaultScheduledDateGenerator();
|
||||||
private final LoanAccountDomainService loanAccountDomainService;
|
private final LoanAccountDomainService loanAccountDomainService;
|
||||||
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
|
private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository;
|
||||||
|
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
|
||||||
/**
|
|
||||||
* LoanRescheduleRequestWritePlatformServiceImpl constructor
|
|
||||||
*
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
@Autowired
|
|
||||||
public LoanRescheduleRequestWritePlatformServiceImpl(final CodeValueRepositoryWrapper codeValueRepositoryWrapper,
|
|
||||||
final PlatformSecurityContext platformSecurityContext,
|
|
||||||
final LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator,
|
|
||||||
final LoanRescheduleRequestRepository loanRescheduleRequestRepository,
|
|
||||||
final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository,
|
|
||||||
final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
|
|
||||||
final LoanTransactionRepository loanTransactionRepository,
|
|
||||||
final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanRepositoryWrapper loanRepositoryWrapper,
|
|
||||||
final LoanAssembler loanAssembler, final LoanUtilService loanUtilService,
|
|
||||||
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
|
|
||||||
final LoanScheduleGeneratorFactory loanScheduleFactory, final LoanSummaryWrapper loanSummaryWrapper,
|
|
||||||
final AccountTransfersWritePlatformService accountTransfersWritePlatformService,
|
|
||||||
final LoanAccountDomainService loanAccountDomainService,
|
|
||||||
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository) {
|
|
||||||
this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
|
|
||||||
this.platformSecurityContext = platformSecurityContext;
|
|
||||||
this.loanRescheduleRequestDataValidator = loanRescheduleRequestDataValidator;
|
|
||||||
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
|
|
||||||
this.loanRepaymentScheduleHistoryRepository = loanRepaymentScheduleHistoryRepository;
|
|
||||||
this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
|
|
||||||
this.loanTransactionRepository = loanTransactionRepository;
|
|
||||||
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
|
|
||||||
this.loanRepositoryWrapper = loanRepositoryWrapper;
|
|
||||||
this.loanAssembler = loanAssembler;
|
|
||||||
this.loanUtilService = loanUtilService;
|
|
||||||
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
|
|
||||||
this.loanScheduleFactory = loanScheduleFactory;
|
|
||||||
this.loanSummaryWrapper = loanSummaryWrapper;
|
|
||||||
this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
|
|
||||||
this.loanAccountDomainService = loanAccountDomainService;
|
|
||||||
this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create a new instance of the LoanRescheduleRequest object from the JsonCommand object and persist
|
* create a new instance of the LoanRescheduleRequest object from the JsonCommand object and persist
|
||||||
@@ -490,6 +454,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
|||||||
loan.addLoanTransaction(mapEntry.getValue());
|
loan.addLoanTransaction(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||||
|
|
||||||
|
|||||||
+7
@@ -155,6 +155,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
private final ExternalIdFactory externalIdFactory;
|
private final ExternalIdFactory externalIdFactory;
|
||||||
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
||||||
private final LoanChargeAssembler loanChargeAssembler;
|
private final LoanChargeAssembler loanChargeAssembler;
|
||||||
|
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
|
||||||
|
|
||||||
private static boolean isPartOfThisInstallment(LoanCharge loanCharge, LoanRepaymentScheduleInstallment e) {
|
private static boolean isPartOfThisInstallment(LoanCharge loanCharge, LoanRepaymentScheduleInstallment e) {
|
||||||
return e.getFromDate().isBefore(loanCharge.getDueDate()) && !loanCharge.getDueDate().isAfter(e.getDueDate());
|
return e.getFromDate().isBefore(loanCharge.getDueDate()) && !loanCharge.getDueDate().isAfter(e.getDueDate());
|
||||||
@@ -246,6 +247,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
loan.addLoanTransaction(mapEntry.getValue());
|
loan.addLoanTransaction(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
this.loanRepositoryWrapper.save(loan);
|
this.loanRepositoryWrapper.save(loan);
|
||||||
}
|
}
|
||||||
@@ -740,6 +743,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
loan.addLoanTransaction(mapEntry.getValue());
|
loan.addLoanTransaction(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1085,6 +1090,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
loan.addLoanTransaction(mapEntry.getValue());
|
loan.addLoanTransaction(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
@@ -241,6 +241,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
|
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
|
||||||
private final LoanAccountLockService loanAccountLockService;
|
private final LoanAccountLockService loanAccountLockService;
|
||||||
private final ExternalIdFactory externalIdFactory;
|
private final ExternalIdFactory externalIdFactory;
|
||||||
|
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -457,6 +458,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
this.loanTransactionRepository.save(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
// auto create standing instruction
|
// auto create standing instruction
|
||||||
@@ -732,6 +735,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
this.loanTransactionRepository.save(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||||
}
|
}
|
||||||
@@ -1129,6 +1134,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
loan.addLoanTransaction(mapEntry.getValue());
|
loan.addLoanTransaction(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String noteText = command.stringValueOfParameterNamed("note");
|
final String noteText = command.stringValueOfParameterNamed("note");
|
||||||
@@ -1360,6 +1367,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
loan.addLoanTransaction(mapEntry.getValue());
|
loan.addLoanTransaction(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String noteText = command.stringValueOfParameterNamed("note");
|
final String noteText = command.stringValueOfParameterNamed("note");
|
||||||
@@ -2128,6 +2137,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
this.loanTransactionRepository.save(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||||
|
|
||||||
@@ -2268,8 +2279,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
if (command.entityId() != null && changedTransactionDetail != null) {
|
if (command.entityId() != null && changedTransactionDetail != null) {
|
||||||
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||||
|
this.loanTransactionRepository.save(mapEntry.getValue());
|
||||||
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||||
createLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
createLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||||
@@ -2326,6 +2340,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
loan.addLoanTransaction(mapEntry.getValue());
|
loan.addLoanTransaction(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
|
// Trigger transaction replayed event
|
||||||
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||||
loanAccountDomainService.recalculateAccruals(loan);
|
loanAccountDomainService.recalculateAccruals(loan);
|
||||||
|
|||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
|
|
||||||
|
public interface ReplayedTransactionBusinessEventService {
|
||||||
|
|
||||||
|
void raiseTransactionReplayedEvents(ChangedTransactionDetail changedTransactionDetail);
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* 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.util.Map;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ReplayedTransactionBusinessEventServiceImpl implements ReplayedTransactionBusinessEventService {
|
||||||
|
|
||||||
|
private final BusinessEventNotifierService businessEventNotifierService;
|
||||||
|
private final LoanTransactionRepository loanTransactionRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void raiseTransactionReplayedEvents(final ChangedTransactionDetail changedTransactionDetail) {
|
||||||
|
if (changedTransactionDetail == null || changedTransactionDetail.getNewTransactionMappings().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Extra safety net to avoid event leaking
|
||||||
|
try {
|
||||||
|
businessEventNotifierService.startExternalEventRecording();
|
||||||
|
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||||
|
LoanTransaction oldTransaction = loanTransactionRepository.findById(mapEntry.getKey())
|
||||||
|
.orElseThrow(() -> new LoanTransactionNotFoundException(mapEntry.getKey()));
|
||||||
|
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(oldTransaction);
|
||||||
|
data.setNewTransactionDetail(mapEntry.getValue());
|
||||||
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||||
|
}
|
||||||
|
businessEventNotifierService.stopExternalEventRecording();
|
||||||
|
} catch (Exception e) {
|
||||||
|
businessEventNotifierService.resetEventRecording();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.lenient;
|
import static org.mockito.Mockito.lenient;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import io.cucumber.java8.En;
|
import io.cucumber.java8.En;
|
||||||
@@ -39,6 +40,7 @@ import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
|||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||||
import org.apache.fineract.mix.data.MixTaxonomyData;
|
import org.apache.fineract.mix.data.MixTaxonomyData;
|
||||||
|
import org.mockito.Mockito;
|
||||||
import org.springframework.beans.factory.BeanCreationException;
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
import org.springframework.beans.factory.ListableBeanFactory;
|
import org.springframework.beans.factory.ListableBeanFactory;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
@@ -147,6 +149,14 @@ public class COBBusinessStepServiceStepDefinitions implements En {
|
|||||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Then("throw exception COBBusinessStepService.run method with verification", () -> {
|
||||||
|
assertThrows(BusinessStepException.class, () -> {
|
||||||
|
resultItem = this.businessStepService.run(this.executionMap, this.item);
|
||||||
|
});
|
||||||
|
verify(businessEventNotifierService, Mockito.times(1)).resetEventRecording();
|
||||||
|
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||||
|
});
|
||||||
|
|
||||||
Then("The COBBusinessStepService.getCOBBusinessStepMap result exception", () -> {
|
Then("The COBBusinessStepService.getCOBBusinessStepMap result exception", () -> {
|
||||||
assertThrows(BeanCreationException.class, () -> {
|
assertThrows(BeanCreationException.class, () -> {
|
||||||
this.businessStepService.getCOBBusinessStepMap(this.clazz, this.jobName);
|
this.businessStepService.getCOBBusinessStepMap(this.clazz, this.jobName);
|
||||||
|
|||||||
+4
@@ -24,6 +24,8 @@ import static org.mockito.Mockito.lenient;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
import io.cucumber.java8.En;
|
import io.cucumber.java8.En;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
|
import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
|
||||||
import org.springframework.batch.repeat.RepeatStatus;
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
@@ -56,12 +58,14 @@ public class InitialisationTaskletStepDefinitions implements En {
|
|||||||
Then("InitialisationTasklet.execute result should match", () -> {
|
Then("InitialisationTasklet.execute result should match", () -> {
|
||||||
assertEquals(RepeatStatus.FINISHED, resultItem);
|
assertEquals(RepeatStatus.FINISHED, resultItem);
|
||||||
assertEquals(appUser, SecurityContextHolder.getContext().getAuthentication().getPrincipal());
|
assertEquals(appUser, SecurityContextHolder.getContext().getAuthentication().getPrincipal());
|
||||||
|
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||||
});
|
});
|
||||||
|
|
||||||
Then("throw exception InitialisationTasklet.execute method", () -> {
|
Then("throw exception InitialisationTasklet.execute method", () -> {
|
||||||
assertThrows(RuntimeException.class, () -> {
|
assertThrows(RuntimeException.class, () -> {
|
||||||
resultItem = this.initialisationTasklet.execute(null, null);
|
resultItem = this.initialisationTasklet.execute(null, null);
|
||||||
});
|
});
|
||||||
|
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* 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 static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ReplayedTransactionBusinessEventServiceIntegrationTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BusinessEventNotifierService businessEventNotifierService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoanTransactionRepository loanTransactionRepository;
|
||||||
|
|
||||||
|
private ReplayedTransactionBusinessEventService underTest;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
underTest = new ReplayedTransactionBusinessEventServiceImpl(businessEventNotifierService, loanTransactionRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWhenParamIsNull() {
|
||||||
|
// given
|
||||||
|
ChangedTransactionDetail changedTransactionDetail = null;
|
||||||
|
// when
|
||||||
|
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
|
// then
|
||||||
|
verify(businessEventNotifierService, Mockito.times(0)).startExternalEventRecording();
|
||||||
|
verify(businessEventNotifierService, Mockito.times(0))
|
||||||
|
.notifyPostBusinessEvent(Mockito.any(LoanAdjustTransactionBusinessEvent.class));
|
||||||
|
verify(businessEventNotifierService, Mockito.times(0)).stopExternalEventRecording();
|
||||||
|
verify(businessEventNotifierService, Mockito.times(0)).resetEventRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWhenParamHasNoMapping() {
|
||||||
|
// given
|
||||||
|
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
||||||
|
// when
|
||||||
|
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
|
// then
|
||||||
|
verify(businessEventNotifierService, Mockito.times(0)).startExternalEventRecording();
|
||||||
|
verify(businessEventNotifierService, Mockito.times(0))
|
||||||
|
.notifyPostBusinessEvent(Mockito.any(LoanAdjustTransactionBusinessEvent.class));
|
||||||
|
verify(businessEventNotifierService, Mockito.times(0)).stopExternalEventRecording();
|
||||||
|
verify(businessEventNotifierService, Mockito.times(0)).resetEventRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWhenParamHasOneNewTransaction() {
|
||||||
|
// given
|
||||||
|
LoanTransaction oldLoanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
|
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
|
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
|
||||||
|
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
||||||
|
changedTransactionDetail.getNewTransactionMappings().put(1L, newLoanTransaction);
|
||||||
|
// when
|
||||||
|
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
|
// then
|
||||||
|
verify(businessEventNotifierService, Mockito.times(1)).startExternalEventRecording();
|
||||||
|
verify(businessEventNotifierService, Mockito.times(1))
|
||||||
|
.notifyPostBusinessEvent(Mockito.any(LoanAdjustTransactionBusinessEvent.class));
|
||||||
|
verify(businessEventNotifierService, Mockito.times(1)).stopExternalEventRecording();
|
||||||
|
verify(businessEventNotifierService, Mockito.times(0)).resetEventRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWhenParamHasTwoNewTransaction() {
|
||||||
|
// given
|
||||||
|
LoanTransaction oldLoanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
|
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
|
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
|
||||||
|
lenient().when(loanTransactionRepository.findById(2L)).thenReturn(Optional.of(oldLoanTransaction));
|
||||||
|
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
||||||
|
changedTransactionDetail.getNewTransactionMappings().put(1L, newLoanTransaction);
|
||||||
|
changedTransactionDetail.getNewTransactionMappings().put(2L, newLoanTransaction);
|
||||||
|
// when
|
||||||
|
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
|
// then
|
||||||
|
verify(businessEventNotifierService, Mockito.times(1)).startExternalEventRecording();
|
||||||
|
verify(businessEventNotifierService, Mockito.times(2))
|
||||||
|
.notifyPostBusinessEvent(Mockito.any(LoanAdjustTransactionBusinessEvent.class));
|
||||||
|
verify(businessEventNotifierService, Mockito.times(1)).stopExternalEventRecording();
|
||||||
|
verify(businessEventNotifierService, Mockito.times(0)).resetEventRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWhenParamHasError() {
|
||||||
|
// given
|
||||||
|
doThrow(new RuntimeException()).when(businessEventNotifierService)
|
||||||
|
.notifyPostBusinessEvent(Mockito.any(LoanAdjustTransactionBusinessEvent.class));
|
||||||
|
LoanTransaction oldLoanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
|
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
|
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
|
||||||
|
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
||||||
|
changedTransactionDetail.getNewTransactionMappings().put(1L, newLoanTransaction);
|
||||||
|
// when
|
||||||
|
assertThrows(RuntimeException.class, () -> underTest.raiseTransactionReplayedEvents(changedTransactionDetail));
|
||||||
|
// then
|
||||||
|
verify(businessEventNotifierService, Mockito.times(1)).startExternalEventRecording();
|
||||||
|
verify(businessEventNotifierService, Mockito.times(1))
|
||||||
|
.notifyPostBusinessEvent(Mockito.any(LoanAdjustTransactionBusinessEvent.class));
|
||||||
|
verify(businessEventNotifierService, Mockito.times(0)).stopExternalEventRecording();
|
||||||
|
verify(businessEventNotifierService, Mockito.times(1)).resetEventRecording();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,14 @@ Feature: COB
|
|||||||
Examples:
|
Examples:
|
||||||
|executionMap|
|
|executionMap|
|
||||||
|null|
|
|null|
|
||||||
|
|
||||||
|
@cob
|
||||||
|
Scenario Outline: COB Business Step Service - run test failure
|
||||||
|
Given The COBBusinessStepService.run method with executeMap <executionMap>
|
||||||
|
Then throw exception COBBusinessStepService.run method with verification
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|executionMap|
|
||||||
|2,notExist|
|
|2,notExist|
|
||||||
|3,|
|
|3,|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user