This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 22282ded00 FINERACT-2181: Rework
"createMissingAccrualTransactionDuringChargeOffIfNeeded" to avoid flushing and
triggering business event as part of Transaction processor
22282ded00 is described below
commit 22282ded00d382d8fac54ab79893a404efada379
Author: mariiaKraievska <[email protected]>
AuthorDate: Mon Mar 10 12:50:03 2025 +0200
FINERACT-2181: Rework
"createMissingAccrualTransactionDuringChargeOffIfNeeded" to avoid flushing and
triggering business event as part of Transaction processor
---
.../AbstractAuditableWithUTCDateTimeCustom.java | 4 +-
.../TransactionChangeData.java} | 19 +--
.../domain/ChangedTransactionDetail.java | 44 ++++++-
.../portfolio/loanaccount/domain/Loan.java | 17 ++-
...tLoanRepaymentScheduleTransactionProcessor.java | 27 +++--
.../LoanRepaymentScheduleTransactionProcessor.java | 4 +-
...eplayedTransactionBusinessEventServiceImpl.java | 31 +++--
.../service/ReprocessLoanTransactionsService.java | 6 +
.../ReprocessLoanTransactionsServiceImpl.java | 48 +++++++-
...dvancedPaymentScheduleTransactionProcessor.java | 127 +++++++++++----------
...cedPaymentScheduleTransactionProcessorTest.java | 16 ++-
.../LoanWritePlatformServiceJpaRepositoryImpl.java | 9 +-
.../ProgressiveLoanInterestRefundServiceImpl.java | 5 +-
.../ProgressiveLoanSummaryDataProvider.java | 11 +-
.../starter/LoanAccountAutoStarter.java | 8 +-
...sactionBusinessEventServiceIntegrationTest.java | 20 ++--
16 files changed, 260 insertions(+), 136 deletions(-)
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java
index 9798cbf8ea..6612602bfd 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java
@@ -52,11 +52,11 @@ public abstract class
AbstractAuditableWithUTCDateTimeCustom<T extends Serializa
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))
private Long createdBy;
- @Column(name = CREATED_DATE_DB_FIELD, nullable = false)
+ @Column(name = CREATED_DATE_DB_FIELD, updatable = false, nullable = false)
@Setter(onMethod = @__(@Override))
private OffsetDateTime createdDate;
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ChangedTransactionDetail.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/TransactionChangeData.java
similarity index 65%
copy from
fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ChangedTransactionDetail.java
copy to
fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/TransactionChangeData.java
index 79e6e74d54..66b29c0a2e 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ChangedTransactionDetail.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/TransactionChangeData.java
@@ -16,20 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.fineract.portfolio.loanaccount.domain;
+package org.apache.fineract.portfolio.loanaccount.data;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import lombok.AllArgsConstructor;
import lombok.Getter;
+import lombok.Setter;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
/**
- * Stores details of {@link LoanTransaction}'s that were reversed or newly
created
+ * Represents a transaction change, storing both the old reversed transaction
(if any) and the new transaction.
*/
@Getter
-public class ChangedTransactionDetail {
-
- private final Map<Long, LoanTransaction> newTransactionMappings = new
LinkedHashMap<>();
-
- private final Map<LoanTransaction, Long> currentTransactionToOldId = new
LinkedHashMap<>();
+@Setter
+@AllArgsConstructor
+public class TransactionChangeData {
+ private LoanTransaction oldTransaction;
+ private LoanTransaction newTransaction;
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ChangedTransactionDetail.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ChangedTransactionDetail.java
index 79e6e74d54..ad4245fc0e 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ChangedTransactionDetail.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ChangedTransactionDetail.java
@@ -18,9 +18,12 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
import lombok.Getter;
+import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
/**
* Stores details of {@link LoanTransaction}'s that were reversed or newly
created
@@ -28,8 +31,41 @@ import lombok.Getter;
@Getter
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));
+ }
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index feccefffca..240bb33bbb 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -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.OutstandingAmountsDTO;
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.MoneyHolder;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
@@ -719,10 +720,12 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
ChangedTransactionDetail changedTransactionDetail =
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(),
getRepaymentScheduleInstallments(),
getActiveCharges());
- for (final Map.Entry<Long, LoanTransaction> mapEntry :
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
- mapEntry.getValue().updateLoan(this);
+ for (TransactionChangeData change :
changedTransactionDetail.getTransactionChanges()) {
+ 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();
return changedTransactionDetail;
}
@@ -2656,14 +2659,16 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
ChangedTransactionDetail changedTransactionDetail =
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(),
getRepaymentScheduleInstallments(),
getActiveCharges());
- for (final Map.Entry<Long, LoanTransaction> mapEntry :
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
- mapEntry.getValue().updateLoan(this);
+ for (TransactionChangeData change :
changedTransactionDetail.getTransactionChanges()) {
+ change.getNewTransaction().updateLoan(this);
}
/*
* 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.
*/
-
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
+ final List<LoanTransaction> newTransactions =
changedTransactionDetail.getTransactionChanges().stream()
+ .map(TransactionChangeData::getNewTransaction).toList();
+ this.loanTransactions.addAll(newTransactions);
updateLoanSummaryDerivedFields();
return changedTransactionDetail;
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index 722328cd25..765df9f396 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -25,7 +25,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Optional;
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.Money;
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.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
@@ -187,7 +187,7 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
**/
if (newLoanTransaction.isReversed()) {
loanTransaction.reverse();
-
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(),
loanTransaction);
+ changedTransactionDetail.addTransactionChange(new
TransactionChangeData(loanTransaction, loanTransaction));
} else if
(LoanTransaction.transactionAmountsMatch(currency, loanTransaction,
newLoanTransaction)) {
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(
newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
@@ -264,7 +264,7 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
}
@Override
- public void processLatestTransaction(final LoanTransaction
loanTransaction, final TransactionCtx ctx) {
+ public ChangedTransactionDetail processLatestTransaction(final
LoanTransaction loanTransaction, final TransactionCtx ctx) {
switch (loanTransaction.getTypeOf()) {
case WRITEOFF -> handleWriteOff(loanTransaction,
ctx.getCurrency(), ctx.getInstallments());
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
@@ -421,10 +422,12 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
private void
reprocessChargebackTransactionRelation(ChangedTransactionDetail
changedTransactionDetail,
List<LoanTransaction> transactionsToBeProcessed) {
-
List<LoanTransaction> mergedTransactionList =
getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
- for (Map.Entry<Long, LoanTransaction> entry :
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
- if (entry.getValue().isChargeback()) {
+ for (TransactionChangeData change :
changedTransactionDetail.getTransactionChanges()) {
+ LoanTransaction newTransaction = change.getNewTransaction();
+ LoanTransaction oldTransaction = change.getOldTransaction();
+
+ if (newTransaction.isChargeback()) {
for (LoanTransaction loanTransaction : mergedTransactionList) {
if (loanTransaction.isReversed()) {
continue;
@@ -433,8 +436,9 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
LoanTransactionRelation oldLoanTransactionRelation = null;
for (LoanTransactionRelation transactionRelation :
loanTransaction.getLoanTransactionRelations()) {
if
(LoanTransactionRelationTypeEnum.CHARGEBACK.equals(transactionRelation.getRelationType())
- &&
entry.getKey().equals(transactionRelation.getToTransaction().getId())) {
- newLoanTransactionRelation =
LoanTransactionRelation.linkToTransaction(loanTransaction, entry.getValue(),
+ && oldTransaction != null &&
oldTransaction.getId() != null
+ &&
oldTransaction.getId().equals(transactionRelation.getToTransaction().getId())) {
+ newLoanTransactionRelation =
LoanTransactionRelation.linkToTransaction(loanTransaction, newTransaction,
LoanTransactionRelationTypeEnum.CHARGEBACK);
oldLoanTransactionRelation = transactionRelation;
break;
@@ -511,8 +515,9 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
private List<LoanTransaction>
getMergedTransactionList(List<LoanTransaction> transactionList,
ChangedTransactionDetail changedTransactionDetail) {
- List<LoanTransaction> mergedList = new
ArrayList<>(changedTransactionDetail.getNewTransactionMappings().values());
- mergedList.addAll(new ArrayList<>(transactionList));
+ List<LoanTransaction> mergedList = new ArrayList<>(
+
changedTransactionDetail.getTransactionChanges().stream().map(TransactionChangeData::getNewTransaction).toList());
+ mergedList.addAll(transactionList);
return mergedList;
}
@@ -525,7 +530,7 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
// Adding Replayed relation from newly created transaction to reversed
transaction
newLoanTransaction.getLoanTransactionRelations().add(
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,
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
index f49635f629..411f26309d 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
@@ -39,8 +39,10 @@ public interface LoanRepaymentScheduleTransactionProcessor {
/**
* Provides support for processing the latest transaction (which should be
the latest transaction) against the loan
* 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
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceImpl.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceImpl.java
index 6ac3ca3d18..f31cbe6923 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceImpl.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceImpl.java
@@ -18,14 +18,15 @@
*/
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.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.portfolio.loanaccount.data.TransactionChangeData;
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;
@RequiredArgsConstructor
public class ReplayedTransactionBusinessEventServiceImpl implements
ReplayedTransactionBusinessEventService {
@@ -35,18 +36,30 @@ public class ReplayedTransactionBusinessEventServiceImpl
implements ReplayedTran
@Override
public void raiseTransactionReplayedEvents(final ChangedTransactionDetail
changedTransactionDetail) {
- if (changedTransactionDetail == null ||
changedTransactionDetail.getNewTransactionMappings().isEmpty()) {
+ if (changedTransactionDetail == null ||
changedTransactionDetail.getTransactionChanges().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));
+
+ for (TransactionChangeData change :
changedTransactionDetail.getTransactionChanges()) {
+ final LoanTransaction newTransaction =
change.getNewTransaction();
+ final LoanTransaction oldTransaction =
change.getOldTransaction();
+
+ 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();
} catch (Exception e) {
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java
index bc84ad8048..ed11c5ee7f 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java
@@ -19,17 +19,23 @@
package org.apache.fineract.portfolio.loanaccount.service;
import java.time.LocalDate;
+import java.util.List;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
public interface ReprocessLoanTransactionsService {
void reprocessTransactions(Loan loan);
+ void reprocessTransactions(Loan loan, List<LoanTransaction>
loanTransactions);
+
void reprocessTransactionsWithPostTransactionChecks(Loan loan, LocalDate
transactionDate);
void processPostDisbursementTransactions(Loan loan);
void removeLoanCharge(Loan loan, LoanCharge loanCharge);
+ void processLatestTransaction(LoanTransaction loanTransaction, Loan loan);
+
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
index d29867e529..334fedbc1c 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
@@ -19,14 +19,18 @@
package org.apache.fineract.portfolio.loanaccount.service;
import java.time.LocalDate;
-import java.util.Map;
+import java.util.List;
import lombok.RequiredArgsConstructor;
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.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
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;
@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
public void reprocessTransactionsWithPostTransactionChecks(final Loan
loan, final LocalDate transactionDate) {
final ChangedTransactionDetail changedTransactionDetail =
loan.reprocessTransactionsWithPostTransactionChecks(transactionDate);
@@ -63,12 +83,30 @@ public class ReprocessLoanTransactionsServiceImpl
implements ReprocessLoanTransa
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) {
- for (final Map.Entry<Long, LoanTransaction> mapEntry :
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
-
loanAccountTransfersService.updateLoanTransaction(mapEntry.getKey(),
mapEntry.getValue());
+ for (TransactionChangeData change :
changedTransactionDetail.getTransactionChanges()) {
+ final LoanTransaction newTransaction = change.getNewTransaction();
+ final LoanTransaction oldTransaction = change.getOldTransaction();
+
+
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newTransaction);
+
+ if (oldTransaction != null) {
+
loanAccountTransfersService.updateLoanTransaction(oldTransaction.getId(),
newTransaction);
+ }
}
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
}
-
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 14dde4885b..6d25be7b39 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -38,7 +38,6 @@ import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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.ExternalIdFactory;
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.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
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.Loan;
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.LoanTransactionRelation;
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.reaging.LoanReAgeParameter;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
@@ -120,9 +116,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
private final EMICalculator emiCalculator;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final InterestRefundService interestRefundService;
- private final LoanTransactionRepository loanTransactionRepository;
private final ExternalIdFactory externalIdFactory;
- private final BusinessEventNotifierService businessEventNotifierService;
@Override
public String getCode() {
@@ -227,11 +221,15 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
}
}
}
- Map<Long, LoanTransaction> newTransactionMappings =
changedTransactionDetail.getNewTransactionMappings();
- for (Long oldTransactionId : newTransactionMappings.keySet()) {
- LoanTransaction oldTransaction =
loanTransactions.stream().filter(e ->
oldTransactionId.equals(e.getId())).findFirst().get();
- LoanTransaction newTransaction =
newTransactionMappings.get(oldTransactionId);
- createNewTransaction(oldTransaction, newTransaction, ctx);
+ final List<TransactionChangeData> transactionChanges =
changedTransactionDetail.getTransactionChanges();
+
+ for (TransactionChangeData change : transactionChanges) {
+ LoanTransaction oldTransaction = change.getOldTransaction();
+ LoanTransaction newTransaction = change.getNewTransaction();
+
+ if (oldTransaction != null) {
+ createNewTransaction(oldTransaction, newTransaction, ctx);
+ }
}
recalculateInterestForDate(targetDate, ctx);
List<LoanTransaction> txs = changeOperations.stream() //
@@ -263,9 +261,12 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
}
@NotNull
- private static LoanTransaction
getProcessedTransaction(ChangedTransactionDetail changedTransactionDetail,
LoanTransaction transaction) {
- LoanTransaction newTransaction =
changedTransactionDetail.getNewTransactionMappings().get(transaction.getId());
- return newTransaction == null ? transaction : newTransaction;
+ private static LoanTransaction getProcessedTransaction(final
ChangedTransactionDetail changedTransactionDetail,
+ final LoanTransaction transaction) {
+ 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,
@@ -298,7 +299,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
}
@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 (ctx instanceof ProgressiveTransactionCtx
progressiveTransactionCtx) {
recalculateInterestForDate(loanTransaction.getTransactionDate(),
progressiveTransactionCtx);
@@ -320,10 +321,9 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
case REAGE -> handleReAge(loanTransaction, ctx);
case ACCRUAL_ACTIVITY -> calculateAccrualActivity(loanTransaction,
ctx);
// TODO: Cover rest of the transaction types
- default -> {
- log.warn("Unhandled transaction processing for transaction
type: {}", loanTransaction.getTypeOf());
- }
+ default -> log.warn("Unhandled transaction processing for
transaction type: {}", loanTransaction.getTypeOf());
}
+ return ctx.getChangedTransactionDetail();
}
private void handleInterestRefund(LoanTransaction loanTransaction,
TransactionCtx ctx) {
@@ -408,14 +408,17 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
ChangedTransactionDetail changedTransactionDetail =
ctx.getChangedTransactionDetail();
Long chargebackId = chargebackTransaction.getId(); // this the normal
case without reverse-replay
if (changedTransactionDetail != null) {
+ final List<TransactionChangeData> transactionChanges =
changedTransactionDetail.getTransactionChanges();
if (chargebackId == null) {
// 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;
- Collection<LoanTransaction> updatedTransactions =
changedTransactionDetail.getNewTransactionMappings().values();
- Optional<LoanTransaction> fromTransaction =
updatedTransactions.stream()
+ Optional<LoanTransaction> fromTransaction =
changedTransactionDetail.getTransactionChanges().stream()
+ .map(TransactionChangeData::getNewTransaction)
.filter(tr ->
tr.getLoanTransactionRelations().stream().anyMatch(hasMatchingToLoanTransaction(toId,
CHARGEBACK))
|| tr.getLoanTransactionRelations().stream()
.anyMatch(this.hasMatchingToLoanTransaction(chargebackTransaction, CHARGEBACK)))
@@ -656,16 +659,22 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
// Remove the current chargeback from the list
allTransactions.remove(chargebackTransaction);
- if (ctx.getChangedTransactionDetail() != null) {
- Long oldId =
ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().get(chargebackTransaction);
- allTransactions.stream().filter(tr -> Objects.equals(tr.getId(),
oldId)).findFirst().ifPresent(allTransactions::remove);
- }
+ final ChangedTransactionDetail changedTransactionDetail =
ctx.getChangedTransactionDetail();
+ if (changedTransactionDetail != null) {
+ final List<TransactionChangeData> transactionChanges =
changedTransactionDetail.getTransactionChanges();
+
+ transactionChanges.stream().filter(change ->
change.getNewTransaction().equals(chargebackTransaction))
+
.map(TransactionChangeData::getOldTransaction).filter(Objects::nonNull).findFirst().ifPresent(allTransactions::remove);
+
+ // Add the replayed transactions and remove their old version
before the replay
+ for (TransactionChangeData change : transactionChanges) {
+ LoanTransaction oldTransaction = change.getOldTransaction();
+ LoanTransaction newTransaction = change.getNewTransaction();
- // Add the replayed transactions and remove their old version before
the replay
- if (ctx.getChangedTransactionDetail() != null &&
ctx.getChangedTransactionDetail().getNewTransactionMappings() != null) {
- for (Long id :
ctx.getChangedTransactionDetail().getNewTransactionMappings().keySet()) {
- allTransactions.stream().filter(tr ->
Objects.equals(tr.getId(), id)).findFirst().ifPresent(allTransactions::remove);
-
allTransactions.add(ctx.getChangedTransactionDetail().getNewTransactionMappings().get(id));
+ 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
// changed.
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
processLatestTransaction(processTransaction, ctx);
@@ -855,7 +864,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
boolean isNew = transaction.getId() == null;
if (!isNew) {
processTransaction =
transaction.copyTransactionPropertiesAndMappings();
-
ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().put(processTransaction,
transaction.getId());
+ ctx.getChangedTransactionDetail().addTransactionChange(new
TransactionChangeData(transaction, processTransaction));
}
processTransaction.setOverPayments(overpayment =
MathUtil.minus(overpayment, processAmount));
overpaymentHolder.setMoneyObject(ctxOverpayment =
MathUtil.minus(ctxOverpayment, processAmount));
@@ -891,9 +900,11 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
private LoanTransaction checkRegisteredNewTransaction(LoanTransaction
newTransaction, TransactionCtx ctx) {
ChangedTransactionDetail changedTransactionDetail =
ctx.getChangedTransactionDetail();
- Long oldTransactionId =
changedTransactionDetail.getCurrentTransactionToOldId().get(newTransaction);
- if (oldTransactionId != null) {
- LoanTransaction oldTransaction =
newTransaction.getLoan().getLoanTransaction(e ->
oldTransactionId.equals(e.getId()));
+ Optional<TransactionChangeData> transactionChange =
changedTransactionDetail.getTransactionChanges().stream()
+ .filter(change ->
change.getNewTransaction().equals(newTransaction)).findFirst();
+
+ if (transactionChange.isPresent()) {
+ LoanTransaction oldTransaction =
transactionChange.get().getOldTransaction();
LoanTransaction applicableTransaction =
useOldTransactionIfApplicable(oldTransaction, newTransaction, ctx);
if (applicableTransaction != null) {
return applicableTransaction;
@@ -911,9 +922,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
newTransaction.copyLoanTransactionRelations(oldTransaction.getLoanTransactionRelations());
- ChangedTransactionDetail changedTransactionDetail =
ctx.getChangedTransactionDetail();
-
changedTransactionDetail.getNewTransactionMappings().put(oldTransaction.getId(),
newTransaction);
-
changedTransactionDetail.getCurrentTransactionToOldId().put(newTransaction,
oldTransaction.getId());
+ ctx.getChangedTransactionDetail().addTransactionChange(new
TransactionChangeData(oldTransaction, newTransaction));
return newTransaction;
}
@@ -922,13 +931,12 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
TransactionCtx ctx) {
MonetaryCurrency currency = ctx.getCurrency();
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
* reverse-replayed. If so, reverse the original transaction and
update changedTransactionDetail accordingly to
* 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()));
boolean amountMatch =
LoanTransaction.transactionAmountsMatch(currency, oldTransaction,
newTransaction);
if (!alreadyProcessed && amountMatch) {
@@ -937,8 +945,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
.updateLoanTransactionToRepaymentScheduleMappings(newTransaction.getLoanTransactionToRepaymentScheduleMappings());
oldTransaction.updateLoanChargePaidMappings(newTransaction.getLoanChargesPaid());
}
-
changedTransactionDetail.getCurrentTransactionToOldId().remove(newTransaction);
- newTransactionMappings.remove(oldTransaction.getId());
+ changedTransactionDetail.removeTransactionChange(newTransaction);
return oldTransaction;
}
return null;
@@ -956,8 +963,11 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
.forEach(newRelation ->
oldTransaction.getLoanTransactionRelations().stream()
.filter(oldRelation ->
LoanTransactionRelationTypeEnum.RELATED.equals(oldRelation.getRelationType()))
.findFirst().map(oldRelation ->
oldRelation.getToTransaction().getId())
- .ifPresent(oldToTransactionId ->
newRelation.setToTransaction(
-
ctx.getChangedTransactionDetail().getNewTransactionMappings().get(oldToTransactionId))));
+ .flatMap(oldToTransactionId ->
ctx.getChangedTransactionDetail().getTransactionChanges().stream()
+ .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
@@ -1355,8 +1365,8 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
final BigDecimal newInterest =
getInterestTillChargeOffForPeriod(loanTransaction.getLoan(),
loanTransaction.getTransactionDate(),
transactionCtx);
- createMissingAccrualTransactionDuringChargeOffIfNeeded(newInterest,
loanTransaction.getLoan(),
- loanTransaction.getTransactionDate());
+ createMissingAccrualTransactionDuringChargeOffIfNeeded(newInterest,
loanTransaction, loanTransaction.getTransactionDate(),
+ transactionCtx);
loanTransaction.resetDerivedComponents();
// determine how much is outstanding total and breakdown for
principal, interest and charges
@@ -2390,8 +2400,9 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
return interestTillChargeOff;
}
- private void createMissingAccrualTransactionDuringChargeOffIfNeeded(final
BigDecimal newInterest, final Loan loan,
- final LocalDate chargeOffDate) {
+ private void createMissingAccrualTransactionDuringChargeOffIfNeeded(final
BigDecimal newInterest,
+ final LoanTransaction chargeOffTransaction, final LocalDate
chargeOffDate, final TransactionCtx ctx) {
+ final Loan loan = chargeOffTransaction.getLoan();
final List<LoanRepaymentScheduleInstallment> relevantInstallments =
loan.getRepaymentScheduleInstallments().stream()
.filter(i -> !i.getFromDate().isAfter(chargeOffDate)).toList();
@@ -2400,10 +2411,14 @@ public class
AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
}
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);
- 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) {
return;
@@ -2419,13 +2434,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
missingAccrualAmount.abs(), ZERO, ZERO,
externalIdFactory.create());
}
- loan.addLoanTransaction(newAccrualTransaction);
- loanTransactionRepository.saveAndFlush(newAccrualTransaction);
-
- if (newAccrualTransaction.isAccrual()) {
- businessEventNotifierService.notifyPostBusinessEvent(new
LoanAccrualTransactionCreatedBusinessEvent(newAccrualTransaction));
- } else {
- businessEventNotifierService.notifyPostBusinessEvent(new
LoanAccrualAdjustmentTransactionBusinessEvent(newAccrualTransaction));
- }
+
ctx.getChangedTransactionDetail().addNewTransactionChangeBeforeExistingOne(new
TransactionChangeData(null, newAccrualTransaction),
+ chargeOffTransaction);
}
}
diff --git
a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
index 16bc868be6..f6552d7c16 100644
---
a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
+++
b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
@@ -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.Money;
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.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
@@ -111,7 +112,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
@BeforeEach
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.setActionContext(ActionContext.DEFAULT);
@@ -658,11 +659,14 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
when(relation.getRelationType()).thenReturn(LoanTransactionRelationTypeEnum.CHARGEBACK);
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);
+
when(changedTransactionDetail.getTransactionChanges()).thenReturn(List.of(transactionChange));
+ TransactionCtx ctx = mock(TransactionCtx.class);
when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
-
when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed,
123L));
-
when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of(122L,
repayment1, 121L, repayment2));
// when
LoanTransaction originalTransaction =
underTest.findChargebackOriginalTransaction(chargebackReplayed, ctx);
@@ -692,8 +696,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
TransactionCtx ctx = mock(TransactionCtx.class);
ChangedTransactionDetail changedTransactionDetail =
mock(ChangedTransactionDetail.class);
when(ctx.getChangedTransactionDetail()).thenReturn(changedTransactionDetail);
-
when(changedTransactionDetail.getCurrentTransactionToOldId()).thenReturn(Map.of(chargebackReplayed,
123L));
-
when(changedTransactionDetail.getNewTransactionMappings()).thenReturn(Map.of());
+ when(changedTransactionDetail.getTransactionChanges())
+ .thenReturn(List.of(new
TransactionChangeData(originalChargeback, chargebackReplayed)));
// when
LoanTransaction originalTransaction =
underTest.findChargebackOriginalTransaction(chargebackReplayed, ctx);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index f12731bc93..dc689e91ba 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -2917,14 +2917,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl
implements LoanWritePlatf
final ScheduleGeneratorDTO scheduleGeneratorDTO =
this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan,
scheduleGeneratorDTO);
}
+ final List<LoanTransaction> loanTransactions =
loan.retrieveListOfTransactionsForReprocessing();
+ loanTransactions.add(chargeOffTransaction);
+ reprocessLoanTransactionsService.reprocessTransactions(loan,
loanTransactions);
loan.addLoanTransaction(chargeOffTransaction);
- reprocessLoanTransactionsService.reprocessTransactions(loan);
} else {
+
reprocessLoanTransactionsService.processLatestTransaction(chargeOffTransaction,
loan);
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);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java
index 5079af3242..062d0b4b4f 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java
@@ -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.Money;
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.Loan;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -92,7 +93,9 @@ public class ProgressiveLoanInterestRefundServiceImpl
implements InterestRefundS
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel>
reprocessResult = processor
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(),
relatedRefundTransactionDate, transactionsToReprocess,
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();
return emiCalculator.getSumOfDueInterestsOnDate(modelAfter,
relatedRefundTransactionDate);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java
index e539e945e0..e1c9e0c45f 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java
@@ -23,6 +23,7 @@ import java.time.LocalDate;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
@@ -97,11 +98,11 @@ public class ProgressiveLoanSummaryDataProvider extends
CommonLoanSummaryDataPro
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate,
transactionsToReprocess,
loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
ProgressiveLoanInterestScheduleModel model =
changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight();
- if
(!changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft().getCurrentTransactionToOldId().isEmpty()
- ||
!changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft().getNewTransactionMappings()
- .isEmpty()) {
- List<Long> replayedTransactions =
changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft()
-
.getNewTransactionMappings().keySet().stream().toList();
+ final List<Long> replayedTransactions =
changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft()
+ .getTransactionChanges().stream().filter(change ->
change.getOldTransaction() != null)
+ .map(change ->
change.getNewTransaction().getId()).filter(Objects::nonNull).toList();
+
+ if (!replayedTransactions.isEmpty()) {
log.warn("Reprocessed transactions show differences: There
are unsaved changes of the following transactions: {}",
replayedTransactions);
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
index 900daa6afc..8737fb81d8 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
@@ -20,10 +20,8 @@ package org.apache.fineract.portfolio.loanaccount.starter;
import java.util.List;
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.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.impl.AdvancedPaymentScheduleTransactionProcessor;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
@@ -112,10 +110,8 @@ public class LoanAccountAutoStarter {
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
public AdvancedPaymentScheduleTransactionProcessor
advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator,
LoanRepositoryWrapper loanRepositoryWrapper,
- @Lazy ProgressiveLoanInterestRefundServiceImpl
progressiveLoanInterestRefundService,
- LoanTransactionRepository loanTransactionRepository,
ExternalIdFactory externalIdFactory,
- @Lazy BusinessEventNotifierService businessEventNotifierService) {
+ @Lazy ProgressiveLoanInterestRefundServiceImpl
progressiveLoanInterestRefundService, ExternalIdFactory externalIdFactory) {
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator,
loanRepositoryWrapper, progressiveLoanInterestRefundService,
- loanTransactionRepository, externalIdFactory,
businessEventNotifierService);
+ externalIdFactory);
}
}
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceIntegrationTest.java
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceIntegrationTest.java
index 9517041801..de3f6f5565 100644
---
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceIntegrationTest.java
+++
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceIntegrationTest.java
@@ -27,6 +27,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
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.data.TransactionChangeData;
import
org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
@@ -88,8 +89,9 @@ class ReplayedTransactionBusinessEventServiceIntegrationTest {
LoanTransaction oldLoanTransaction =
Mockito.mock(LoanTransaction.class);
LoanTransaction newLoanTransaction =
Mockito.mock(LoanTransaction.class);
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
+ lenient().when(oldLoanTransaction.getId()).thenReturn(1L);
ChangedTransactionDetail changedTransactionDetail = new
ChangedTransactionDetail();
- changedTransactionDetail.getNewTransactionMappings().put(1L,
newLoanTransaction);
+ changedTransactionDetail.addTransactionChange(new
TransactionChangeData(oldLoanTransaction, newLoanTransaction));
// when
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
// then
@@ -103,13 +105,16 @@ class
ReplayedTransactionBusinessEventServiceIntegrationTest {
@Test
public void testWhenParamHasTwoNewTransaction() {
// 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);
-
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
-
lenient().when(loanTransactionRepository.findById(2L)).thenReturn(Optional.of(oldLoanTransaction));
+
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction1));
+
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.getNewTransactionMappings().put(1L,
newLoanTransaction);
- changedTransactionDetail.getNewTransactionMappings().put(2L,
newLoanTransaction);
+ changedTransactionDetail.addTransactionChange(new
TransactionChangeData(oldLoanTransaction1, newLoanTransaction));
+ changedTransactionDetail.addTransactionChange(new
TransactionChangeData(oldLoanTransaction2, newLoanTransaction));
// when
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
// then
@@ -128,8 +133,9 @@ class
ReplayedTransactionBusinessEventServiceIntegrationTest {
LoanTransaction oldLoanTransaction =
Mockito.mock(LoanTransaction.class);
LoanTransaction newLoanTransaction =
Mockito.mock(LoanTransaction.class);
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
+ lenient().when(oldLoanTransaction.getId()).thenReturn(1L);
ChangedTransactionDetail changedTransactionDetail = new
ChangedTransactionDetail();
- changedTransactionDetail.getNewTransactionMappings().put(1L,
newLoanTransaction);
+ changedTransactionDetail.addTransactionChange(new
TransactionChangeData(oldLoanTransaction, newLoanTransaction));
// when
assertThrows(RuntimeException.class, () ->
underTest.raiseTransactionReplayedEvents(changedTransactionDetail));
// then