FINERACT-2181: Rework "createMissingAccrualTransactionDuringChargeOffIfNeeded" to avoid flushing and triggering business event as part of Transaction processor
This commit is contained in:
committed by
Adam Saghy
parent
166c1981fb
commit
22282ded00
+2
-2
@@ -52,11 +52,11 @@ public abstract class AbstractAuditableWithUTCDateTimeCustom<T extends Serializa
|
|||||||
|
|
||||||
private static final long serialVersionUID = 141481953116476081L;
|
private static final long serialVersionUID = 141481953116476081L;
|
||||||
|
|
||||||
@Column(name = CREATED_BY_DB_FIELD, nullable = false)
|
@Column(name = CREATED_BY_DB_FIELD, updatable = false, nullable = false)
|
||||||
@Setter(onMethod = @__(@Override))
|
@Setter(onMethod = @__(@Override))
|
||||||
private Long createdBy;
|
private Long createdBy;
|
||||||
|
|
||||||
@Column(name = CREATED_DATE_DB_FIELD, nullable = false)
|
@Column(name = CREATED_DATE_DB_FIELD, updatable = false, nullable = false)
|
||||||
@Setter(onMethod = @__(@Override))
|
@Setter(onMethod = @__(@Override))
|
||||||
private OffsetDateTime createdDate;
|
private OffsetDateTime createdDate;
|
||||||
|
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 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.data;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a transaction change, storing both the old reversed transaction (if any) and the new transaction.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TransactionChangeData {
|
||||||
|
|
||||||
|
private LoanTransaction oldTransaction;
|
||||||
|
private LoanTransaction newTransaction;
|
||||||
|
}
|
||||||
+40
-4
@@ -18,9 +18,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.domain;
|
package org.apache.fineract.portfolio.loanaccount.domain;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.ArrayList;
|
||||||
import java.util.Map;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores details of {@link LoanTransaction}'s that were reversed or newly created
|
* Stores details of {@link LoanTransaction}'s that were reversed or newly created
|
||||||
@@ -28,8 +31,41 @@ import lombok.Getter;
|
|||||||
@Getter
|
@Getter
|
||||||
public class ChangedTransactionDetail {
|
public class ChangedTransactionDetail {
|
||||||
|
|
||||||
private final Map<Long, LoanTransaction> newTransactionMappings = new LinkedHashMap<>();
|
private final List<TransactionChangeData> transactionChanges = new ArrayList<>();
|
||||||
|
|
||||||
private final Map<LoanTransaction, Long> currentTransactionToOldId = new LinkedHashMap<>();
|
public void addTransactionChange(final TransactionChangeData transactionChangeData) {
|
||||||
|
for (TransactionChangeData change : transactionChanges) {
|
||||||
|
if (transactionChangeData.getOldTransaction() != null && change.getOldTransaction() != null
|
||||||
|
&& Objects.equals(change.getOldTransaction().getId(), transactionChangeData.getOldTransaction().getId())) {
|
||||||
|
change.setOldTransaction(transactionChangeData.getOldTransaction());
|
||||||
|
change.setNewTransaction(transactionChangeData.getNewTransaction());
|
||||||
|
return;
|
||||||
|
} else if (transactionChangeData.getOldTransaction() == null && change.getOldTransaction() == null
|
||||||
|
&& change.getNewTransaction() != null
|
||||||
|
&& Objects.equals(change.getNewTransaction().getId(), transactionChangeData.getNewTransaction().getId())) {
|
||||||
|
change.setNewTransaction(transactionChangeData.getNewTransaction());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transactionChanges.add(transactionChangeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNewTransactionChangeBeforeExistingOne(final TransactionChangeData newTransactionChange,
|
||||||
|
final LoanTransaction existingLoanTransaction) {
|
||||||
|
if (existingLoanTransaction != null) {
|
||||||
|
final Optional<TransactionChangeData> existingChange = transactionChanges.stream().filter(
|
||||||
|
change -> change.getNewTransaction() != null && Objects.equals(change.getNewTransaction(), existingLoanTransaction))
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
if (existingChange.isPresent()) {
|
||||||
|
transactionChanges.add(transactionChanges.indexOf(existingChange.get()), newTransactionChange);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transactionChanges.add(newTransactionChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeTransactionChange(final LoanTransaction newTransaction) {
|
||||||
|
transactionChanges.removeIf(change -> change.getNewTransaction().equals(newTransaction));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-6
@@ -107,6 +107,7 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||||
@@ -719,10 +720,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
||||||
getActiveCharges());
|
getActiveCharges());
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
||||||
mapEntry.getValue().updateLoan(this);
|
change.getNewTransaction().updateLoan(this);
|
||||||
}
|
}
|
||||||
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
||||||
|
.map(TransactionChangeData::getNewTransaction).toList();
|
||||||
|
this.loanTransactions.addAll(newTransactions);
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
return changedTransactionDetail;
|
return changedTransactionDetail;
|
||||||
}
|
}
|
||||||
@@ -2656,14 +2659,16 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
||||||
getActiveCharges());
|
getActiveCharges());
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
||||||
mapEntry.getValue().updateLoan(this);
|
change.getNewTransaction().updateLoan(this);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Commented since throwing exception if external id present for one of the transactions. for this need to save
|
* Commented since throwing exception if external id present for one of the transactions. for this need to save
|
||||||
* the reversed transactions first and then new transactions.
|
* the reversed transactions first and then new transactions.
|
||||||
*/
|
*/
|
||||||
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
||||||
|
.map(TransactionChangeData::getNewTransaction).toList();
|
||||||
|
this.loanTransactions.addAll(newTransactions);
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
|
|
||||||
return changedTransactionDetail;
|
return changedTransactionDetail;
|
||||||
|
|||||||
+16
-11
@@ -25,7 +25,6 @@ import java.util.Collections;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -34,6 +33,7 @@ import org.apache.fineract.infrastructure.core.service.MathUtil;
|
|||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
@@ -187,7 +187,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
**/
|
**/
|
||||||
if (newLoanTransaction.isReversed()) {
|
if (newLoanTransaction.isReversed()) {
|
||||||
loanTransaction.reverse();
|
loanTransaction.reverse();
|
||||||
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), loanTransaction);
|
changedTransactionDetail.addTransactionChange(new TransactionChangeData(loanTransaction, loanTransaction));
|
||||||
} else if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
|
} else if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
|
||||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(
|
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(
|
||||||
newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
|
newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
|
||||||
@@ -264,7 +264,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processLatestTransaction(final LoanTransaction loanTransaction, final TransactionCtx ctx) {
|
public ChangedTransactionDetail processLatestTransaction(final LoanTransaction loanTransaction, final TransactionCtx ctx) {
|
||||||
switch (loanTransaction.getTypeOf()) {
|
switch (loanTransaction.getTypeOf()) {
|
||||||
case WRITEOFF -> handleWriteOff(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
|
case WRITEOFF -> handleWriteOff(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
|
||||||
case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getCharges());
|
case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getCharges());
|
||||||
@@ -288,6 +288,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return ctx.getChangedTransactionDetail();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -421,10 +422,12 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
|
|
||||||
private void reprocessChargebackTransactionRelation(ChangedTransactionDetail changedTransactionDetail,
|
private void reprocessChargebackTransactionRelation(ChangedTransactionDetail changedTransactionDetail,
|
||||||
List<LoanTransaction> transactionsToBeProcessed) {
|
List<LoanTransaction> transactionsToBeProcessed) {
|
||||||
|
|
||||||
List<LoanTransaction> mergedTransactionList = getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
|
List<LoanTransaction> mergedTransactionList = getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
|
||||||
for (Map.Entry<Long, LoanTransaction> entry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
||||||
if (entry.getValue().isChargeback()) {
|
LoanTransaction newTransaction = change.getNewTransaction();
|
||||||
|
LoanTransaction oldTransaction = change.getOldTransaction();
|
||||||
|
|
||||||
|
if (newTransaction.isChargeback()) {
|
||||||
for (LoanTransaction loanTransaction : mergedTransactionList) {
|
for (LoanTransaction loanTransaction : mergedTransactionList) {
|
||||||
if (loanTransaction.isReversed()) {
|
if (loanTransaction.isReversed()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -433,8 +436,9 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
LoanTransactionRelation oldLoanTransactionRelation = null;
|
LoanTransactionRelation oldLoanTransactionRelation = null;
|
||||||
for (LoanTransactionRelation transactionRelation : loanTransaction.getLoanTransactionRelations()) {
|
for (LoanTransactionRelation transactionRelation : loanTransaction.getLoanTransactionRelations()) {
|
||||||
if (LoanTransactionRelationTypeEnum.CHARGEBACK.equals(transactionRelation.getRelationType())
|
if (LoanTransactionRelationTypeEnum.CHARGEBACK.equals(transactionRelation.getRelationType())
|
||||||
&& entry.getKey().equals(transactionRelation.getToTransaction().getId())) {
|
&& oldTransaction != null && oldTransaction.getId() != null
|
||||||
newLoanTransactionRelation = LoanTransactionRelation.linkToTransaction(loanTransaction, entry.getValue(),
|
&& oldTransaction.getId().equals(transactionRelation.getToTransaction().getId())) {
|
||||||
|
newLoanTransactionRelation = LoanTransactionRelation.linkToTransaction(loanTransaction, newTransaction,
|
||||||
LoanTransactionRelationTypeEnum.CHARGEBACK);
|
LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||||
oldLoanTransactionRelation = transactionRelation;
|
oldLoanTransactionRelation = transactionRelation;
|
||||||
break;
|
break;
|
||||||
@@ -511,8 +515,9 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
|
|
||||||
private List<LoanTransaction> getMergedTransactionList(List<LoanTransaction> transactionList,
|
private List<LoanTransaction> getMergedTransactionList(List<LoanTransaction> transactionList,
|
||||||
ChangedTransactionDetail changedTransactionDetail) {
|
ChangedTransactionDetail changedTransactionDetail) {
|
||||||
List<LoanTransaction> mergedList = new ArrayList<>(changedTransactionDetail.getNewTransactionMappings().values());
|
List<LoanTransaction> mergedList = new ArrayList<>(
|
||||||
mergedList.addAll(new ArrayList<>(transactionList));
|
changedTransactionDetail.getTransactionChanges().stream().map(TransactionChangeData::getNewTransaction).toList());
|
||||||
|
mergedList.addAll(transactionList);
|
||||||
return mergedList;
|
return mergedList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +530,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
// Adding Replayed relation from newly created transaction to reversed transaction
|
// Adding Replayed relation from newly created transaction to reversed transaction
|
||||||
newLoanTransaction.getLoanTransactionRelations().add(
|
newLoanTransaction.getLoanTransactionRelations().add(
|
||||||
LoanTransactionRelation.linkToTransaction(newLoanTransaction, loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
LoanTransactionRelation.linkToTransaction(newLoanTransaction, loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
||||||
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), newLoanTransaction);
|
changedTransactionDetail.addTransactionChange(new TransactionChangeData(loanTransaction, newLoanTransaction));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void processCreditTransaction(LoanTransaction loanTransaction, MoneyHolder overpaymentHolder, MonetaryCurrency currency,
|
protected void processCreditTransaction(LoanTransaction loanTransaction, MoneyHolder overpaymentHolder, MonetaryCurrency currency,
|
||||||
|
|||||||
+3
-1
@@ -39,8 +39,10 @@ public interface LoanRepaymentScheduleTransactionProcessor {
|
|||||||
/**
|
/**
|
||||||
* Provides support for processing the latest transaction (which should be the latest transaction) against the loan
|
* Provides support for processing the latest transaction (which should be the latest transaction) against the loan
|
||||||
* schedule.
|
* schedule.
|
||||||
|
*
|
||||||
|
* @return ChangedTransactionDetail
|
||||||
*/
|
*/
|
||||||
void processLatestTransaction(LoanTransaction loanTransaction, TransactionCtx ctx);
|
ChangedTransactionDetail processLatestTransaction(LoanTransaction loanTransaction, TransactionCtx ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides support for passing all {@link LoanTransaction}'s so it will completely re-process the entire loan
|
* Provides support for passing all {@link LoanTransaction}'s so it will completely re-process the entire loan
|
||||||
|
|||||||
+22
-9
@@ -18,14 +18,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualAdjustmentTransactionBusinessEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ReplayedTransactionBusinessEventServiceImpl implements ReplayedTransactionBusinessEventService {
|
public class ReplayedTransactionBusinessEventServiceImpl implements ReplayedTransactionBusinessEventService {
|
||||||
@@ -35,18 +36,30 @@ public class ReplayedTransactionBusinessEventServiceImpl implements ReplayedTran
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void raiseTransactionReplayedEvents(final ChangedTransactionDetail changedTransactionDetail) {
|
public void raiseTransactionReplayedEvents(final ChangedTransactionDetail changedTransactionDetail) {
|
||||||
if (changedTransactionDetail == null || changedTransactionDetail.getNewTransactionMappings().isEmpty()) {
|
if (changedTransactionDetail == null || changedTransactionDetail.getTransactionChanges().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Extra safety net to avoid event leaking
|
// Extra safety net to avoid event leaking
|
||||||
try {
|
try {
|
||||||
businessEventNotifierService.startExternalEventRecording();
|
businessEventNotifierService.startExternalEventRecording();
|
||||||
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
|
||||||
LoanTransaction oldTransaction = loanTransactionRepository.findById(mapEntry.getKey())
|
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
||||||
.orElseThrow(() -> new LoanTransactionNotFoundException(mapEntry.getKey()));
|
final LoanTransaction newTransaction = change.getNewTransaction();
|
||||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(oldTransaction);
|
final LoanTransaction oldTransaction = change.getOldTransaction();
|
||||||
data.setNewTransactionDetail(mapEntry.getValue());
|
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
if (oldTransaction != null) {
|
||||||
|
final LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(oldTransaction);
|
||||||
|
data.setNewTransactionDetail(newTransaction);
|
||||||
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||||
|
} else {
|
||||||
|
if (newTransaction.isAccrual()) {
|
||||||
|
businessEventNotifierService
|
||||||
|
.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(newTransaction));
|
||||||
|
} else {
|
||||||
|
businessEventNotifierService
|
||||||
|
.notifyPostBusinessEvent(new LoanAccrualAdjustmentTransactionBusinessEvent(newTransaction));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
businessEventNotifierService.stopExternalEventRecording();
|
businessEventNotifierService.stopExternalEventRecording();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
+6
@@ -19,17 +19,23 @@
|
|||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
|
||||||
public interface ReprocessLoanTransactionsService {
|
public interface ReprocessLoanTransactionsService {
|
||||||
|
|
||||||
void reprocessTransactions(Loan loan);
|
void reprocessTransactions(Loan loan);
|
||||||
|
|
||||||
|
void reprocessTransactions(Loan loan, List<LoanTransaction> loanTransactions);
|
||||||
|
|
||||||
void reprocessTransactionsWithPostTransactionChecks(Loan loan, LocalDate transactionDate);
|
void reprocessTransactionsWithPostTransactionChecks(Loan loan, LocalDate transactionDate);
|
||||||
|
|
||||||
void processPostDisbursementTransactions(Loan loan);
|
void processPostDisbursementTransactions(Loan loan);
|
||||||
|
|
||||||
void removeLoanCharge(Loan loan, LoanCharge loanCharge);
|
void removeLoanCharge(Loan loan, LoanCharge loanCharge);
|
||||||
|
|
||||||
|
void processLatestTransaction(LoanTransaction loanTransaction, Loan loan);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-5
@@ -19,14 +19,18 @@
|
|||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Map;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.portfolio.interestpauses.service.LoanAccountTransfersService;
|
import org.apache.fineract.portfolio.interestpauses.service.LoanAccountTransfersService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -45,6 +49,22 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reprocessTransactions(final Loan loan, final List<LoanTransaction> loanTransactions) {
|
||||||
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor();
|
||||||
|
final ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||||
|
loan.getDisbursementDate(), loanTransactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
||||||
|
loan.getActiveCharges());
|
||||||
|
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
||||||
|
change.getNewTransaction().updateLoan(loan);
|
||||||
|
}
|
||||||
|
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
||||||
|
.map(TransactionChangeData::getNewTransaction).toList();
|
||||||
|
loan.getLoanTransactions().addAll(newTransactions);
|
||||||
|
loan.updateLoanSummaryDerivedFields();
|
||||||
|
handleChangedDetail(changedTransactionDetail);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
|
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
|
||||||
final ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactionsWithPostTransactionChecks(transactionDate);
|
final ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactionsWithPostTransactionChecks(transactionDate);
|
||||||
@@ -63,12 +83,30 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
loan.removeLoanCharge(loanCharge).ifPresent(this::handleChangedDetail);
|
loan.removeLoanCharge(loanCharge).ifPresent(this::handleChangedDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processLatestTransaction(final LoanTransaction loanTransaction, final Loan loan) {
|
||||||
|
final ChangedTransactionDetail changedTransactionDetail = loan.getTransactionProcessor().processLatestTransaction(loanTransaction,
|
||||||
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail()));
|
||||||
|
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
||||||
|
.map(TransactionChangeData::getNewTransaction).peek(transaction -> transaction.updateLoan(loan)).toList();
|
||||||
|
loan.getLoanTransactions().addAll(newTransactions);
|
||||||
|
|
||||||
|
loan.updateLoanSummaryDerivedFields();
|
||||||
|
handleChangedDetail(changedTransactionDetail);
|
||||||
|
}
|
||||||
|
|
||||||
private void handleChangedDetail(final ChangedTransactionDetail changedTransactionDetail) {
|
private void handleChangedDetail(final ChangedTransactionDetail changedTransactionDetail) {
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
|
||||||
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
final LoanTransaction newTransaction = change.getNewTransaction();
|
||||||
loanAccountTransfersService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
final LoanTransaction oldTransaction = change.getOldTransaction();
|
||||||
|
|
||||||
|
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newTransaction);
|
||||||
|
|
||||||
|
if (oldTransaction != null) {
|
||||||
|
loanAccountTransfersService.updateLoanTransaction(oldTransaction.getId(), newTransaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-59
@@ -38,7 +38,6 @@ import java.time.LocalDate;
|
|||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -65,13 +64,11 @@ import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
|||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualAdjustmentTransactionBusinessEvent;
|
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
|
|
||||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
@@ -88,7 +85,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
|
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
@@ -120,9 +116,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
private final EMICalculator emiCalculator;
|
private final EMICalculator emiCalculator;
|
||||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||||
private final InterestRefundService interestRefundService;
|
private final InterestRefundService interestRefundService;
|
||||||
private final LoanTransactionRepository loanTransactionRepository;
|
|
||||||
private final ExternalIdFactory externalIdFactory;
|
private final ExternalIdFactory externalIdFactory;
|
||||||
private final BusinessEventNotifierService businessEventNotifierService;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCode() {
|
public String getCode() {
|
||||||
@@ -227,11 +221,15 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Map<Long, LoanTransaction> newTransactionMappings = changedTransactionDetail.getNewTransactionMappings();
|
final List<TransactionChangeData> transactionChanges = changedTransactionDetail.getTransactionChanges();
|
||||||
for (Long oldTransactionId : newTransactionMappings.keySet()) {
|
|
||||||
LoanTransaction oldTransaction = loanTransactions.stream().filter(e -> oldTransactionId.equals(e.getId())).findFirst().get();
|
for (TransactionChangeData change : transactionChanges) {
|
||||||
LoanTransaction newTransaction = newTransactionMappings.get(oldTransactionId);
|
LoanTransaction oldTransaction = change.getOldTransaction();
|
||||||
createNewTransaction(oldTransaction, newTransaction, ctx);
|
LoanTransaction newTransaction = change.getNewTransaction();
|
||||||
|
|
||||||
|
if (oldTransaction != null) {
|
||||||
|
createNewTransaction(oldTransaction, newTransaction, ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
recalculateInterestForDate(targetDate, ctx);
|
recalculateInterestForDate(targetDate, ctx);
|
||||||
List<LoanTransaction> txs = changeOperations.stream() //
|
List<LoanTransaction> txs = changeOperations.stream() //
|
||||||
@@ -263,9 +261,12 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static LoanTransaction getProcessedTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction transaction) {
|
private static LoanTransaction getProcessedTransaction(final ChangedTransactionDetail changedTransactionDetail,
|
||||||
LoanTransaction newTransaction = changedTransactionDetail.getNewTransactionMappings().get(transaction.getId());
|
final LoanTransaction transaction) {
|
||||||
return newTransaction == null ? transaction : newTransaction;
|
return changedTransactionDetail.getTransactionChanges().stream()
|
||||||
|
.filter(change -> change.getOldTransaction() != null && change.getOldTransaction().getId() != null
|
||||||
|
&& change.getOldTransaction().getId().equals(transaction.getId()))
|
||||||
|
.map(TransactionChangeData::getNewTransaction).findFirst().orElse(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processInterestRateChange(final List<LoanRepaymentScheduleInstallment> installments,
|
private void processInterestRateChange(final List<LoanRepaymentScheduleInstallment> installments,
|
||||||
@@ -298,7 +299,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processLatestTransaction(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
public ChangedTransactionDetail processLatestTransaction(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
||||||
// If we are behind, we might need to first recalculate interest
|
// If we are behind, we might need to first recalculate interest
|
||||||
if (ctx instanceof ProgressiveTransactionCtx progressiveTransactionCtx) {
|
if (ctx instanceof ProgressiveTransactionCtx progressiveTransactionCtx) {
|
||||||
recalculateInterestForDate(loanTransaction.getTransactionDate(), progressiveTransactionCtx);
|
recalculateInterestForDate(loanTransaction.getTransactionDate(), progressiveTransactionCtx);
|
||||||
@@ -320,10 +321,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
case REAGE -> handleReAge(loanTransaction, ctx);
|
case REAGE -> handleReAge(loanTransaction, ctx);
|
||||||
case ACCRUAL_ACTIVITY -> calculateAccrualActivity(loanTransaction, ctx);
|
case ACCRUAL_ACTIVITY -> calculateAccrualActivity(loanTransaction, ctx);
|
||||||
// TODO: Cover rest of the transaction types
|
// TODO: Cover rest of the transaction types
|
||||||
default -> {
|
default -> log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf());
|
||||||
log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return ctx.getChangedTransactionDetail();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleInterestRefund(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
private void handleInterestRefund(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
||||||
@@ -408,14 +408,17 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
ChangedTransactionDetail changedTransactionDetail = ctx.getChangedTransactionDetail();
|
ChangedTransactionDetail changedTransactionDetail = ctx.getChangedTransactionDetail();
|
||||||
Long chargebackId = chargebackTransaction.getId(); // this the normal case without reverse-replay
|
Long chargebackId = chargebackTransaction.getId(); // this the normal case without reverse-replay
|
||||||
if (changedTransactionDetail != null) {
|
if (changedTransactionDetail != null) {
|
||||||
|
final List<TransactionChangeData> transactionChanges = changedTransactionDetail.getTransactionChanges();
|
||||||
if (chargebackId == null) {
|
if (chargebackId == null) {
|
||||||
// the chargeback transaction was changed, so we need to look it up from the ctx.
|
// the chargeback transaction was changed, so we need to look it up from the ctx.
|
||||||
chargebackId = changedTransactionDetail.getCurrentTransactionToOldId().get(chargebackTransaction);
|
chargebackId = transactionChanges.stream().filter(change -> change.getNewTransaction().equals(chargebackTransaction))
|
||||||
|
.flatMap(change -> Optional.ofNullable(change.getOldTransaction()).map(AbstractPersistableCustom::getId).stream())
|
||||||
|
.findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Long toId = chargebackId;
|
Long toId = chargebackId;
|
||||||
Collection<LoanTransaction> updatedTransactions = changedTransactionDetail.getNewTransactionMappings().values();
|
Optional<LoanTransaction> fromTransaction = changedTransactionDetail.getTransactionChanges().stream()
|
||||||
Optional<LoanTransaction> fromTransaction = updatedTransactions.stream()
|
.map(TransactionChangeData::getNewTransaction)
|
||||||
.filter(tr -> tr.getLoanTransactionRelations().stream().anyMatch(hasMatchingToLoanTransaction(toId, CHARGEBACK))
|
.filter(tr -> tr.getLoanTransactionRelations().stream().anyMatch(hasMatchingToLoanTransaction(toId, CHARGEBACK))
|
||||||
|| tr.getLoanTransactionRelations().stream()
|
|| tr.getLoanTransactionRelations().stream()
|
||||||
.anyMatch(this.hasMatchingToLoanTransaction(chargebackTransaction, CHARGEBACK)))
|
.anyMatch(this.hasMatchingToLoanTransaction(chargebackTransaction, CHARGEBACK)))
|
||||||
@@ -656,16 +659,22 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
// Remove the current chargeback from the list
|
// Remove the current chargeback from the list
|
||||||
allTransactions.remove(chargebackTransaction);
|
allTransactions.remove(chargebackTransaction);
|
||||||
if (ctx.getChangedTransactionDetail() != null) {
|
final ChangedTransactionDetail changedTransactionDetail = ctx.getChangedTransactionDetail();
|
||||||
Long oldId = ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().get(chargebackTransaction);
|
if (changedTransactionDetail != null) {
|
||||||
allTransactions.stream().filter(tr -> Objects.equals(tr.getId(), oldId)).findFirst().ifPresent(allTransactions::remove);
|
final List<TransactionChangeData> transactionChanges = changedTransactionDetail.getTransactionChanges();
|
||||||
}
|
|
||||||
|
|
||||||
// Add the replayed transactions and remove their old version before the replay
|
transactionChanges.stream().filter(change -> change.getNewTransaction().equals(chargebackTransaction))
|
||||||
if (ctx.getChangedTransactionDetail() != null && ctx.getChangedTransactionDetail().getNewTransactionMappings() != null) {
|
.map(TransactionChangeData::getOldTransaction).filter(Objects::nonNull).findFirst().ifPresent(allTransactions::remove);
|
||||||
for (Long id : ctx.getChangedTransactionDetail().getNewTransactionMappings().keySet()) {
|
|
||||||
allTransactions.stream().filter(tr -> Objects.equals(tr.getId(), id)).findFirst().ifPresent(allTransactions::remove);
|
// Add the replayed transactions and remove their old version before the replay
|
||||||
allTransactions.add(ctx.getChangedTransactionDetail().getNewTransactionMappings().get(id));
|
for (TransactionChangeData change : transactionChanges) {
|
||||||
|
LoanTransaction oldTransaction = change.getOldTransaction();
|
||||||
|
LoanTransaction newTransaction = change.getNewTransaction();
|
||||||
|
|
||||||
|
if (oldTransaction != null) {
|
||||||
|
allTransactions.removeIf(tr -> Objects.equals(tr.getId(), oldTransaction.getId()));
|
||||||
|
}
|
||||||
|
allTransactions.add(newTransaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,7 +832,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
// For existing transactions, check if the re-payment breakup (principal, interest, fees, penalties) has
|
// For existing transactions, check if the re-payment breakup (principal, interest, fees, penalties) has
|
||||||
// changed.
|
// changed.
|
||||||
processTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
|
processTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
|
||||||
ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().put(processTransaction, loanTransaction.getId());
|
ctx.getChangedTransactionDetail().addTransactionChange(new TransactionChangeData(loanTransaction, processTransaction));
|
||||||
}
|
}
|
||||||
// Reset derived component of new loan transaction and re-process transaction
|
// Reset derived component of new loan transaction and re-process transaction
|
||||||
processLatestTransaction(processTransaction, ctx);
|
processLatestTransaction(processTransaction, ctx);
|
||||||
@@ -855,7 +864,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
boolean isNew = transaction.getId() == null;
|
boolean isNew = transaction.getId() == null;
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
processTransaction = transaction.copyTransactionPropertiesAndMappings();
|
processTransaction = transaction.copyTransactionPropertiesAndMappings();
|
||||||
ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().put(processTransaction, transaction.getId());
|
ctx.getChangedTransactionDetail().addTransactionChange(new TransactionChangeData(transaction, processTransaction));
|
||||||
}
|
}
|
||||||
processTransaction.setOverPayments(overpayment = MathUtil.minus(overpayment, processAmount));
|
processTransaction.setOverPayments(overpayment = MathUtil.minus(overpayment, processAmount));
|
||||||
overpaymentHolder.setMoneyObject(ctxOverpayment = MathUtil.minus(ctxOverpayment, processAmount));
|
overpaymentHolder.setMoneyObject(ctxOverpayment = MathUtil.minus(ctxOverpayment, processAmount));
|
||||||
@@ -891,9 +900,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
private LoanTransaction checkRegisteredNewTransaction(LoanTransaction newTransaction, TransactionCtx ctx) {
|
private LoanTransaction checkRegisteredNewTransaction(LoanTransaction newTransaction, TransactionCtx ctx) {
|
||||||
ChangedTransactionDetail changedTransactionDetail = ctx.getChangedTransactionDetail();
|
ChangedTransactionDetail changedTransactionDetail = ctx.getChangedTransactionDetail();
|
||||||
Long oldTransactionId = changedTransactionDetail.getCurrentTransactionToOldId().get(newTransaction);
|
Optional<TransactionChangeData> transactionChange = changedTransactionDetail.getTransactionChanges().stream()
|
||||||
if (oldTransactionId != null) {
|
.filter(change -> change.getNewTransaction().equals(newTransaction)).findFirst();
|
||||||
LoanTransaction oldTransaction = newTransaction.getLoan().getLoanTransaction(e -> oldTransactionId.equals(e.getId()));
|
|
||||||
|
if (transactionChange.isPresent()) {
|
||||||
|
LoanTransaction oldTransaction = transactionChange.get().getOldTransaction();
|
||||||
LoanTransaction applicableTransaction = useOldTransactionIfApplicable(oldTransaction, newTransaction, ctx);
|
LoanTransaction applicableTransaction = useOldTransactionIfApplicable(oldTransaction, newTransaction, ctx);
|
||||||
if (applicableTransaction != null) {
|
if (applicableTransaction != null) {
|
||||||
return applicableTransaction;
|
return applicableTransaction;
|
||||||
@@ -911,9 +922,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
newTransaction.copyLoanTransactionRelations(oldTransaction.getLoanTransactionRelations());
|
newTransaction.copyLoanTransactionRelations(oldTransaction.getLoanTransactionRelations());
|
||||||
|
|
||||||
ChangedTransactionDetail changedTransactionDetail = ctx.getChangedTransactionDetail();
|
ctx.getChangedTransactionDetail().addTransactionChange(new TransactionChangeData(oldTransaction, newTransaction));
|
||||||
changedTransactionDetail.getNewTransactionMappings().put(oldTransaction.getId(), newTransaction);
|
|
||||||
changedTransactionDetail.getCurrentTransactionToOldId().put(newTransaction, oldTransaction.getId());
|
|
||||||
return newTransaction;
|
return newTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -922,13 +931,12 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
TransactionCtx ctx) {
|
TransactionCtx ctx) {
|
||||||
MonetaryCurrency currency = ctx.getCurrency();
|
MonetaryCurrency currency = ctx.getCurrency();
|
||||||
ChangedTransactionDetail changedTransactionDetail = ctx.getChangedTransactionDetail();
|
ChangedTransactionDetail changedTransactionDetail = ctx.getChangedTransactionDetail();
|
||||||
Map<Long, LoanTransaction> newTransactionMappings = changedTransactionDetail.getNewTransactionMappings();
|
|
||||||
/*
|
/*
|
||||||
* Check if the transaction amounts have changed or was there any transaction for the same date which was
|
* Check if the transaction amounts have changed or was there any transaction for the same date which was
|
||||||
* reverse-replayed. If so, reverse the original transaction and update changedTransactionDetail accordingly to
|
* reverse-replayed. If so, reverse the original transaction and update changedTransactionDetail accordingly to
|
||||||
* keep the original order of the transactions.
|
* keep the original order of the transactions.
|
||||||
*/
|
*/
|
||||||
boolean alreadyProcessed = newTransactionMappings.values().stream()
|
boolean alreadyProcessed = changedTransactionDetail.getTransactionChanges().stream().map(TransactionChangeData::getNewTransaction)
|
||||||
.anyMatch(lt -> !lt.equals(newTransaction) && lt.getTransactionDate().equals(oldTransaction.getTransactionDate()));
|
.anyMatch(lt -> !lt.equals(newTransaction) && lt.getTransactionDate().equals(oldTransaction.getTransactionDate()));
|
||||||
boolean amountMatch = LoanTransaction.transactionAmountsMatch(currency, oldTransaction, newTransaction);
|
boolean amountMatch = LoanTransaction.transactionAmountsMatch(currency, oldTransaction, newTransaction);
|
||||||
if (!alreadyProcessed && amountMatch) {
|
if (!alreadyProcessed && amountMatch) {
|
||||||
@@ -937,8 +945,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
.updateLoanTransactionToRepaymentScheduleMappings(newTransaction.getLoanTransactionToRepaymentScheduleMappings());
|
.updateLoanTransactionToRepaymentScheduleMappings(newTransaction.getLoanTransactionToRepaymentScheduleMappings());
|
||||||
oldTransaction.updateLoanChargePaidMappings(newTransaction.getLoanChargesPaid());
|
oldTransaction.updateLoanChargePaidMappings(newTransaction.getLoanChargesPaid());
|
||||||
}
|
}
|
||||||
changedTransactionDetail.getCurrentTransactionToOldId().remove(newTransaction);
|
changedTransactionDetail.removeTransactionChange(newTransaction);
|
||||||
newTransactionMappings.remove(oldTransaction.getId());
|
|
||||||
return oldTransaction;
|
return oldTransaction;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -956,8 +963,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
.forEach(newRelation -> oldTransaction.getLoanTransactionRelations().stream()
|
.forEach(newRelation -> oldTransaction.getLoanTransactionRelations().stream()
|
||||||
.filter(oldRelation -> LoanTransactionRelationTypeEnum.RELATED.equals(oldRelation.getRelationType()))
|
.filter(oldRelation -> LoanTransactionRelationTypeEnum.RELATED.equals(oldRelation.getRelationType()))
|
||||||
.findFirst().map(oldRelation -> oldRelation.getToTransaction().getId())
|
.findFirst().map(oldRelation -> oldRelation.getToTransaction().getId())
|
||||||
.ifPresent(oldToTransactionId -> newRelation.setToTransaction(
|
.flatMap(oldToTransactionId -> ctx.getChangedTransactionDetail().getTransactionChanges().stream()
|
||||||
ctx.getChangedTransactionDetail().getNewTransactionMappings().get(oldToTransactionId))));
|
.filter(change -> change.getOldTransaction().getId() != null
|
||||||
|
&& change.getOldTransaction().getId().equals(oldToTransactionId))
|
||||||
|
.map(TransactionChangeData::getNewTransaction).findFirst())
|
||||||
|
.ifPresent(newRelation::setToTransaction));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding Replayed relation from newly created transaction to reversed transaction
|
// Adding Replayed relation from newly created transaction to reversed transaction
|
||||||
@@ -1355,8 +1365,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
final BigDecimal newInterest = getInterestTillChargeOffForPeriod(loanTransaction.getLoan(), loanTransaction.getTransactionDate(),
|
final BigDecimal newInterest = getInterestTillChargeOffForPeriod(loanTransaction.getLoan(), loanTransaction.getTransactionDate(),
|
||||||
transactionCtx);
|
transactionCtx);
|
||||||
createMissingAccrualTransactionDuringChargeOffIfNeeded(newInterest, loanTransaction.getLoan(),
|
createMissingAccrualTransactionDuringChargeOffIfNeeded(newInterest, loanTransaction, loanTransaction.getTransactionDate(),
|
||||||
loanTransaction.getTransactionDate());
|
transactionCtx);
|
||||||
|
|
||||||
loanTransaction.resetDerivedComponents();
|
loanTransaction.resetDerivedComponents();
|
||||||
// determine how much is outstanding total and breakdown for principal, interest and charges
|
// determine how much is outstanding total and breakdown for principal, interest and charges
|
||||||
@@ -2390,8 +2400,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
return interestTillChargeOff;
|
return interestTillChargeOff;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createMissingAccrualTransactionDuringChargeOffIfNeeded(final BigDecimal newInterest, final Loan loan,
|
private void createMissingAccrualTransactionDuringChargeOffIfNeeded(final BigDecimal newInterest,
|
||||||
final LocalDate chargeOffDate) {
|
final LoanTransaction chargeOffTransaction, final LocalDate chargeOffDate, final TransactionCtx ctx) {
|
||||||
|
final Loan loan = chargeOffTransaction.getLoan();
|
||||||
final List<LoanRepaymentScheduleInstallment> relevantInstallments = loan.getRepaymentScheduleInstallments().stream()
|
final List<LoanRepaymentScheduleInstallment> relevantInstallments = loan.getRepaymentScheduleInstallments().stream()
|
||||||
.filter(i -> !i.getFromDate().isAfter(chargeOffDate)).toList();
|
.filter(i -> !i.getFromDate().isAfter(chargeOffDate)).toList();
|
||||||
|
|
||||||
@@ -2400,10 +2411,14 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
final BigDecimal sumOfAccrualsTillChargeOff = loan.getLoanTransactions().stream()
|
final BigDecimal sumOfAccrualsTillChargeOff = loan.getLoanTransactions().stream()
|
||||||
.filter(lt -> lt.isAccrual() && !lt.getTransactionDate().isAfter(chargeOffDate))
|
.filter(lt -> lt.isAccrual() && !lt.getTransactionDate().isAfter(chargeOffDate) && lt.isNotReversed())
|
||||||
.map(lt -> Optional.ofNullable(lt.getInterestPortion()).orElse(BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
|
.map(lt -> Optional.ofNullable(lt.getInterestPortion()).orElse(BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
|
||||||
final BigDecimal missingAccrualAmount = newInterest.subtract(sumOfAccrualsTillChargeOff);
|
final BigDecimal sumOfAccrualAdjustmentsTillChargeOff = loan.getLoanTransactions().stream()
|
||||||
|
.filter(lt -> lt.isAccrualAdjustment() && !lt.getTransactionDate().isAfter(chargeOffDate) && lt.isNotReversed())
|
||||||
|
.map(lt -> Optional.ofNullable(lt.getInterestPortion()).orElse(BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
|
||||||
|
final BigDecimal missingAccrualAmount = newInterest.subtract(sumOfAccrualsTillChargeOff).add(sumOfAccrualAdjustmentsTillChargeOff);
|
||||||
|
|
||||||
if (missingAccrualAmount.compareTo(BigDecimal.ZERO) == 0) {
|
if (missingAccrualAmount.compareTo(BigDecimal.ZERO) == 0) {
|
||||||
return;
|
return;
|
||||||
@@ -2419,13 +2434,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
missingAccrualAmount.abs(), ZERO, ZERO, externalIdFactory.create());
|
missingAccrualAmount.abs(), ZERO, ZERO, externalIdFactory.create());
|
||||||
}
|
}
|
||||||
|
|
||||||
loan.addLoanTransaction(newAccrualTransaction);
|
ctx.getChangedTransactionDetail().addNewTransactionChangeBeforeExistingOne(new TransactionChangeData(null, newAccrualTransaction),
|
||||||
loanTransactionRepository.saveAndFlush(newAccrualTransaction);
|
chargeOffTransaction);
|
||||||
|
|
||||||
if (newAccrualTransaction.isAccrual()) {
|
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(newAccrualTransaction));
|
|
||||||
} else {
|
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualAdjustmentTransactionBusinessEvent(newAccrualTransaction));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-6
@@ -53,6 +53,7 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
|||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
@@ -111,7 +112,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, null, null, null, null);
|
underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, null, null);
|
||||||
|
|
||||||
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||||
@@ -658,11 +659,14 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
|
||||||
when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
when(repayment2.getLoanTransactionRelations()).thenReturn(Set.of(relation));
|
||||||
|
|
||||||
TransactionCtx ctx = mock(TransactionCtx.class);
|
Loan loan = mock(Loan.class);
|
||||||
|
when(chargebackReplayed.getLoan()).thenReturn(loan);
|
||||||
|
when(loan.getLoanTransactions()).thenReturn(List.of(repayment1, repayment2));
|
||||||
|
TransactionChangeData transactionChange = new TransactionChangeData(originalChargeback, chargebackReplayed);
|
||||||
ChangedTransactionDetail changedTransactionDetail = mock(ChangedTransactionDetail.class);
|
ChangedTransactionDetail changedTransactionDetail = mock(ChangedTransactionDetail.class);
|
||||||
|
when(changedTransactionDetail.getTransactionChanges()).thenReturn(List.of(transactionChange));
|
||||||
|
TransactionCtx ctx = mock(TransactionCtx.class);
|
||||||
when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
|
when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
|
||||||
when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
|
|
||||||
when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of(122L, repayment1, 121L, repayment2));
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
LoanTransaction originalTransaction = underTest.findChargebackOriginalTransaction(chargebackReplayed, ctx);
|
LoanTransaction originalTransaction = underTest.findChargebackOriginalTransaction(chargebackReplayed, ctx);
|
||||||
@@ -692,8 +696,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
TransactionCtx ctx = mock(TransactionCtx.class);
|
TransactionCtx ctx = mock(TransactionCtx.class);
|
||||||
ChangedTransactionDetail changedTransactionDetail = mock(ChangedTransactionDetail.class);
|
ChangedTransactionDetail changedTransactionDetail = mock(ChangedTransactionDetail.class);
|
||||||
when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
|
when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
|
||||||
when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed, 123L));
|
when(changedTransactionDetail.getTransactionChanges())
|
||||||
when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of());
|
.thenReturn(List.of(new TransactionChangeData(originalChargeback, chargebackReplayed)));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
LoanTransaction originalTransaction = underTest.findChargebackOriginalTransaction(chargebackReplayed, ctx);
|
LoanTransaction originalTransaction = underTest.findChargebackOriginalTransaction(chargebackReplayed, ctx);
|
||||||
|
|||||||
+4
-5
@@ -2917,14 +2917,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
|
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
|
||||||
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
|
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
|
||||||
}
|
}
|
||||||
|
final List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsForReprocessing();
|
||||||
|
loanTransactions.add(chargeOffTransaction);
|
||||||
|
reprocessLoanTransactionsService.reprocessTransactions(loan, loanTransactions);
|
||||||
loan.addLoanTransaction(chargeOffTransaction);
|
loan.addLoanTransaction(chargeOffTransaction);
|
||||||
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
|
||||||
} else {
|
} else {
|
||||||
|
reprocessLoanTransactionsService.processLatestTransaction(chargeOffTransaction, loan);
|
||||||
loan.addLoanTransaction(chargeOffTransaction);
|
loan.addLoanTransaction(chargeOffTransaction);
|
||||||
loan.getTransactionProcessor().processLatestTransaction(chargeOffTransaction,
|
|
||||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
|
||||||
loan.updateLoanSummaryDerivedFields();
|
|
||||||
}
|
}
|
||||||
loanTransactionRepository.saveAndFlush(chargeOffTransaction);
|
loanTransactionRepository.saveAndFlush(chargeOffTransaction);
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.core.service.MathUtil;
|
|||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
@@ -92,7 +93,9 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS
|
|||||||
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessResult = processor
|
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessResult = processor
|
||||||
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), relatedRefundTransactionDate, transactionsToReprocess,
|
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), relatedRefundTransactionDate, transactionsToReprocess,
|
||||||
loan.getCurrency(), installmentsToReprocess, loan.getActiveCharges());
|
loan.getCurrency(), installmentsToReprocess, loan.getActiveCharges());
|
||||||
loan.getLoanTransactions().addAll(reprocessResult.getLeft().getCurrentTransactionToOldId().keySet());
|
final List<LoanTransaction> newTransactions = reprocessResult.getLeft().getTransactionChanges().stream()
|
||||||
|
.map(TransactionChangeData::getNewTransaction).toList();
|
||||||
|
loan.getLoanTransactions().addAll(newTransactions);
|
||||||
ProgressiveLoanInterestScheduleModel modelAfter = reprocessResult.getRight();
|
ProgressiveLoanInterestScheduleModel modelAfter = reprocessResult.getRight();
|
||||||
|
|
||||||
return emiCalculator.getSumOfDueInterestsOnDate(modelAfter, relatedRefundTransactionDate);
|
return emiCalculator.getSumOfDueInterestsOnDate(modelAfter, relatedRefundTransactionDate);
|
||||||
|
|||||||
+6
-5
@@ -23,6 +23,7 @@ import java.time.LocalDate;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
@@ -97,11 +98,11 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
|
|||||||
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess,
|
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess,
|
||||||
loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
||||||
ProgressiveLoanInterestScheduleModel model = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight();
|
ProgressiveLoanInterestScheduleModel model = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight();
|
||||||
if (!changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft().getCurrentTransactionToOldId().isEmpty()
|
final List<Long> replayedTransactions = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft()
|
||||||
|| !changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft().getNewTransactionMappings()
|
.getTransactionChanges().stream().filter(change -> change.getOldTransaction() != null)
|
||||||
.isEmpty()) {
|
.map(change -> change.getNewTransaction().getId()).filter(Objects::nonNull).toList();
|
||||||
List<Long> replayedTransactions = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft()
|
|
||||||
.getNewTransactionMappings().keySet().stream().toList();
|
if (!replayedTransactions.isEmpty()) {
|
||||||
log.warn("Reprocessed transactions show differences: There are unsaved changes of the following transactions: {}",
|
log.warn("Reprocessed transactions show differences: There are unsaved changes of the following transactions: {}",
|
||||||
replayedTransactions);
|
replayedTransactions);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-6
@@ -20,10 +20,8 @@ package org.apache.fineract.portfolio.loanaccount.starter;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
|
||||||
@@ -112,10 +110,8 @@ public class LoanAccountAutoStarter {
|
|||||||
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
|
||||||
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator,
|
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator,
|
||||||
LoanRepositoryWrapper loanRepositoryWrapper,
|
LoanRepositoryWrapper loanRepositoryWrapper,
|
||||||
@Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService,
|
@Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService, ExternalIdFactory externalIdFactory) {
|
||||||
LoanTransactionRepository loanTransactionRepository, ExternalIdFactory externalIdFactory,
|
|
||||||
@Lazy BusinessEventNotifierService businessEventNotifierService) {
|
|
||||||
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService,
|
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService,
|
||||||
loanTransactionRepository, externalIdFactory, businessEventNotifierService);
|
externalIdFactory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-7
@@ -27,6 +27,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||||
@@ -88,8 +89,9 @@ class ReplayedTransactionBusinessEventServiceIntegrationTest {
|
|||||||
LoanTransaction oldLoanTransaction = Mockito.mock(LoanTransaction.class);
|
LoanTransaction oldLoanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
|
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
|
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
|
||||||
|
lenient().when(oldLoanTransaction.getId()).thenReturn(1L);
|
||||||
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
||||||
changedTransactionDetail.getNewTransactionMappings().put(1L, newLoanTransaction);
|
changedTransactionDetail.addTransactionChange(new TransactionChangeData(oldLoanTransaction, newLoanTransaction));
|
||||||
// when
|
// when
|
||||||
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
|
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
// then
|
// then
|
||||||
@@ -103,13 +105,16 @@ class ReplayedTransactionBusinessEventServiceIntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testWhenParamHasTwoNewTransaction() {
|
public void testWhenParamHasTwoNewTransaction() {
|
||||||
// given
|
// given
|
||||||
LoanTransaction oldLoanTransaction = Mockito.mock(LoanTransaction.class);
|
LoanTransaction oldLoanTransaction1 = Mockito.mock(LoanTransaction.class);
|
||||||
|
LoanTransaction oldLoanTransaction2 = Mockito.mock(LoanTransaction.class);
|
||||||
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
|
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
|
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction1));
|
||||||
lenient().when(loanTransactionRepository.findById(2L)).thenReturn(Optional.of(oldLoanTransaction));
|
lenient().when(loanTransactionRepository.findById(2L)).thenReturn(Optional.of(oldLoanTransaction2));
|
||||||
|
lenient().when(oldLoanTransaction1.getId()).thenReturn(1L);
|
||||||
|
lenient().when(oldLoanTransaction2.getId()).thenReturn(2L);
|
||||||
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
||||||
changedTransactionDetail.getNewTransactionMappings().put(1L, newLoanTransaction);
|
changedTransactionDetail.addTransactionChange(new TransactionChangeData(oldLoanTransaction1, newLoanTransaction));
|
||||||
changedTransactionDetail.getNewTransactionMappings().put(2L, newLoanTransaction);
|
changedTransactionDetail.addTransactionChange(new TransactionChangeData(oldLoanTransaction2, newLoanTransaction));
|
||||||
// when
|
// when
|
||||||
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
|
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
// then
|
// then
|
||||||
@@ -128,8 +133,9 @@ class ReplayedTransactionBusinessEventServiceIntegrationTest {
|
|||||||
LoanTransaction oldLoanTransaction = Mockito.mock(LoanTransaction.class);
|
LoanTransaction oldLoanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
|
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
|
||||||
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
|
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
|
||||||
|
lenient().when(oldLoanTransaction.getId()).thenReturn(1L);
|
||||||
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
||||||
changedTransactionDetail.getNewTransactionMappings().put(1L, newLoanTransaction);
|
changedTransactionDetail.addTransactionChange(new TransactionChangeData(oldLoanTransaction, newLoanTransaction));
|
||||||
// when
|
// when
|
||||||
assertThrows(RuntimeException.class, () -> underTest.raiseTransactionReplayedEvents(changedTransactionDetail));
|
assertThrows(RuntimeException.class, () -> underTest.raiseTransactionReplayedEvents(changedTransactionDetail));
|
||||||
// then
|
// then
|
||||||
|
|||||||
Reference in New Issue
Block a user