Skip to content

Commit 22282de

Browse files
mariiaKraievskaadamsaghy
authored andcommitted
FINERACT-2181: Rework "createMissingAccrualTransactionDuringChargeOffIfNeeded" to avoid flushing and triggering business event as part of Transaction processor
1 parent 166c198 commit 22282de

File tree

16 files changed

+286
-127
lines changed

16 files changed

+286
-127
lines changed

fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ public abstract class AbstractAuditableWithUTCDateTimeCustom<T extends Serializa
5252

5353
private static final long serialVersionUID = 141481953116476081L;
5454

55-
@Column(name = CREATED_BY_DB_FIELD, nullable = false)
55+
@Column(name = CREATED_BY_DB_FIELD, updatable = false, nullable = false)
5656
@Setter(onMethod = @__(@Override))
5757
private Long createdBy;
5858

59-
@Column(name = CREATED_DATE_DB_FIELD, nullable = false)
59+
@Column(name = CREATED_DATE_DB_FIELD, updatable = false, nullable = false)
6060
@Setter(onMethod = @__(@Override))
6161
private OffsetDateTime createdDate;
6262

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.portfolio.loanaccount.data;
20+
21+
import lombok.AllArgsConstructor;
22+
import lombok.Getter;
23+
import lombok.Setter;
24+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
25+
26+
/**
27+
* Represents a transaction change, storing both the old reversed transaction (if any) and the new transaction.
28+
*/
29+
@Getter
30+
@Setter
31+
@AllArgsConstructor
32+
public class TransactionChangeData {
33+
34+
private LoanTransaction oldTransaction;
35+
private LoanTransaction newTransaction;
36+
}

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/ChangedTransactionDetail.java

+40-4
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,54 @@
1818
*/
1919
package org.apache.fineract.portfolio.loanaccount.domain;
2020

21-
import java.util.LinkedHashMap;
22-
import java.util.Map;
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
import java.util.Objects;
24+
import java.util.Optional;
2325
import lombok.Getter;
26+
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
2427

2528
/**
2629
* Stores details of {@link LoanTransaction}'s that were reversed or newly created
2730
*/
2831
@Getter
2932
public class ChangedTransactionDetail {
3033

31-
private final Map<Long, LoanTransaction> newTransactionMappings = new LinkedHashMap<>();
34+
private final List<TransactionChangeData> transactionChanges = new ArrayList<>();
3235

33-
private final Map<LoanTransaction, Long> currentTransactionToOldId = new LinkedHashMap<>();
36+
public void addTransactionChange(final TransactionChangeData transactionChangeData) {
37+
for (TransactionChangeData change : transactionChanges) {
38+
if (transactionChangeData.getOldTransaction() != null && change.getOldTransaction() != null
39+
&& Objects.equals(change.getOldTransaction().getId(), transactionChangeData.getOldTransaction().getId())) {
40+
change.setOldTransaction(transactionChangeData.getOldTransaction());
41+
change.setNewTransaction(transactionChangeData.getNewTransaction());
42+
return;
43+
} else if (transactionChangeData.getOldTransaction() == null && change.getOldTransaction() == null
44+
&& change.getNewTransaction() != null
45+
&& Objects.equals(change.getNewTransaction().getId(), transactionChangeData.getNewTransaction().getId())) {
46+
change.setNewTransaction(transactionChangeData.getNewTransaction());
47+
return;
48+
}
49+
}
50+
transactionChanges.add(transactionChangeData);
51+
}
3452

53+
public void addNewTransactionChangeBeforeExistingOne(final TransactionChangeData newTransactionChange,
54+
final LoanTransaction existingLoanTransaction) {
55+
if (existingLoanTransaction != null) {
56+
final Optional<TransactionChangeData> existingChange = transactionChanges.stream().filter(
57+
change -> change.getNewTransaction() != null && Objects.equals(change.getNewTransaction(), existingLoanTransaction))
58+
.findFirst();
59+
60+
if (existingChange.isPresent()) {
61+
transactionChanges.add(transactionChanges.indexOf(existingChange.get()), newTransactionChange);
62+
return;
63+
}
64+
}
65+
transactionChanges.add(newTransactionChange);
66+
}
67+
68+
public void removeTransactionChange(final LoanTransaction newTransaction) {
69+
transactionChanges.removeIf(change -> change.getNewTransaction().equals(newTransaction));
70+
}
3571
}

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java

+11-6
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
108108
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
109109
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
110+
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
110111
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
111112
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
112113
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
@@ -719,10 +720,12 @@ public ChangedTransactionDetail reprocessTransactions() {
719720
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
720721
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
721722
getActiveCharges());
722-
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
723-
mapEntry.getValue().updateLoan(this);
723+
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
724+
change.getNewTransaction().updateLoan(this);
724725
}
725-
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
726+
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
727+
.map(TransactionChangeData::getNewTransaction).toList();
728+
this.loanTransactions.addAll(newTransactions);
726729
updateLoanSummaryDerivedFields();
727730
return changedTransactionDetail;
728731
}
@@ -2656,14 +2659,16 @@ public ChangedTransactionDetail processTransactions() {
26562659
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
26572660
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
26582661
getActiveCharges());
2659-
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
2660-
mapEntry.getValue().updateLoan(this);
2662+
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
2663+
change.getNewTransaction().updateLoan(this);
26612664
}
26622665
/*
26632666
* Commented since throwing exception if external id present for one of the transactions. for this need to save
26642667
* the reversed transactions first and then new transactions.
26652668
*/
2666-
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
2669+
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
2670+
.map(TransactionChangeData::getNewTransaction).toList();
2671+
this.loanTransactions.addAll(newTransactions);
26672672
updateLoanSummaryDerivedFields();
26682673

26692674
return changedTransactionDetail;

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java

+16-11
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.util.Comparator;
2626
import java.util.HashSet;
2727
import java.util.List;
28-
import java.util.Map;
2928
import java.util.Objects;
3029
import java.util.Optional;
3130
import java.util.Set;
@@ -34,6 +33,7 @@
3433
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
3534
import org.apache.fineract.organisation.monetary.domain.Money;
3635
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
36+
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
3737
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
3838
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
3939
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
@@ -187,7 +187,7 @@ public ChangedTransactionDetail reprocessLoanTransactions(final LocalDate disbur
187187
**/
188188
if (newLoanTransaction.isReversed()) {
189189
loanTransaction.reverse();
190-
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), loanTransaction);
190+
changedTransactionDetail.addTransactionChange(new TransactionChangeData(loanTransaction, loanTransaction));
191191
} else if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
192192
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(
193193
newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
@@ -264,7 +264,7 @@ private void recalculateAccrualActivityTransaction(ChangedTransactionDetail chan
264264
}
265265

266266
@Override
267-
public void processLatestTransaction(final LoanTransaction loanTransaction, final TransactionCtx ctx) {
267+
public ChangedTransactionDetail processLatestTransaction(final LoanTransaction loanTransaction, final TransactionCtx ctx) {
268268
switch (loanTransaction.getTypeOf()) {
269269
case WRITEOFF -> handleWriteOff(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
270270
case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getCharges());
@@ -288,6 +288,7 @@ public void processLatestTransaction(final LoanTransaction loanTransaction, fina
288288
}
289289
}
290290
}
291+
return ctx.getChangedTransactionDetail();
291292
}
292293

293294
@Override
@@ -421,10 +422,12 @@ private void recalculateChargeOffTransaction(ChangedTransactionDetail changedTra
421422

422423
private void reprocessChargebackTransactionRelation(ChangedTransactionDetail changedTransactionDetail,
423424
List<LoanTransaction> transactionsToBeProcessed) {
424-
425425
List<LoanTransaction> mergedTransactionList = getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
426-
for (Map.Entry<Long, LoanTransaction> entry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
427-
if (entry.getValue().isChargeback()) {
426+
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
427+
LoanTransaction newTransaction = change.getNewTransaction();
428+
LoanTransaction oldTransaction = change.getOldTransaction();
429+
430+
if (newTransaction.isChargeback()) {
428431
for (LoanTransaction loanTransaction : mergedTransactionList) {
429432
if (loanTransaction.isReversed()) {
430433
continue;
@@ -433,8 +436,9 @@ private void reprocessChargebackTransactionRelation(ChangedTransactionDetail cha
433436
LoanTransactionRelation oldLoanTransactionRelation = null;
434437
for (LoanTransactionRelation transactionRelation : loanTransaction.getLoanTransactionRelations()) {
435438
if (LoanTransactionRelationTypeEnum.CHARGEBACK.equals(transactionRelation.getRelationType())
436-
&& entry.getKey().equals(transactionRelation.getToTransaction().getId())) {
437-
newLoanTransactionRelation = LoanTransactionRelation.linkToTransaction(loanTransaction, entry.getValue(),
439+
&& oldTransaction != null && oldTransaction.getId() != null
440+
&& oldTransaction.getId().equals(transactionRelation.getToTransaction().getId())) {
441+
newLoanTransactionRelation = LoanTransactionRelation.linkToTransaction(loanTransaction, newTransaction,
438442
LoanTransactionRelationTypeEnum.CHARGEBACK);
439443
oldLoanTransactionRelation = transactionRelation;
440444
break;
@@ -511,8 +515,9 @@ private void recalculateCreditTransaction(ChangedTransactionDetail changedTransa
511515

512516
private List<LoanTransaction> getMergedTransactionList(List<LoanTransaction> transactionList,
513517
ChangedTransactionDetail changedTransactionDetail) {
514-
List<LoanTransaction> mergedList = new ArrayList<>(changedTransactionDetail.getNewTransactionMappings().values());
515-
mergedList.addAll(new ArrayList<>(transactionList));
518+
List<LoanTransaction> mergedList = new ArrayList<>(
519+
changedTransactionDetail.getTransactionChanges().stream().map(TransactionChangeData::getNewTransaction).toList());
520+
mergedList.addAll(transactionList);
516521
return mergedList;
517522
}
518523

@@ -525,7 +530,7 @@ protected void createNewTransaction(LoanTransaction loanTransaction, LoanTransac
525530
// Adding Replayed relation from newly created transaction to reversed transaction
526531
newLoanTransaction.getLoanTransactionRelations().add(
527532
LoanTransactionRelation.linkToTransaction(newLoanTransaction, loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
528-
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), newLoanTransaction);
533+
changedTransactionDetail.addTransactionChange(new TransactionChangeData(loanTransaction, newLoanTransaction));
529534
}
530535

531536
protected void processCreditTransaction(LoanTransaction loanTransaction, MoneyHolder overpaymentHolder, MonetaryCurrency currency,

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ public interface LoanRepaymentScheduleTransactionProcessor {
3939
/**
4040
* Provides support for processing the latest transaction (which should be the latest transaction) against the loan
4141
* schedule.
42+
*
43+
* @return ChangedTransactionDetail
4244
*/
43-
void processLatestTransaction(LoanTransaction loanTransaction, TransactionCtx ctx);
45+
ChangedTransactionDetail processLatestTransaction(LoanTransaction loanTransaction, TransactionCtx ctx);
4446

4547
/**
4648
* Provides support for passing all {@link LoanTransaction}'s so it will completely re-process the entire loan

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReplayedTransactionBusinessEventServiceImpl.java

+22-9
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818
*/
1919
package org.apache.fineract.portfolio.loanaccount.service;
2020

21-
import java.util.Map;
2221
import lombok.RequiredArgsConstructor;
2322
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
23+
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualAdjustmentTransactionBusinessEvent;
24+
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
2425
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
26+
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
2527
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
2628
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
2729
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
28-
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
2930

3031
@RequiredArgsConstructor
3132
public class ReplayedTransactionBusinessEventServiceImpl implements ReplayedTransactionBusinessEventService {
@@ -35,18 +36,30 @@ public class ReplayedTransactionBusinessEventServiceImpl implements ReplayedTran
3536

3637
@Override
3738
public void raiseTransactionReplayedEvents(final ChangedTransactionDetail changedTransactionDetail) {
38-
if (changedTransactionDetail == null || changedTransactionDetail.getNewTransactionMappings().isEmpty()) {
39+
if (changedTransactionDetail == null || changedTransactionDetail.getTransactionChanges().isEmpty()) {
3940
return;
4041
}
4142
// Extra safety net to avoid event leaking
4243
try {
4344
businessEventNotifierService.startExternalEventRecording();
44-
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
45-
LoanTransaction oldTransaction = loanTransactionRepository.findById(mapEntry.getKey())
46-
.orElseThrow(() -> new LoanTransactionNotFoundException(mapEntry.getKey()));
47-
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(oldTransaction);
48-
data.setNewTransactionDetail(mapEntry.getValue());
49-
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
45+
46+
for (TransactionChangeData change : changedTransactionDetail.getTransactionChanges()) {
47+
final LoanTransaction newTransaction = change.getNewTransaction();
48+
final LoanTransaction oldTransaction = change.getOldTransaction();
49+
50+
if (oldTransaction != null) {
51+
final LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(oldTransaction);
52+
data.setNewTransactionDetail(newTransaction);
53+
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
54+
} else {
55+
if (newTransaction.isAccrual()) {
56+
businessEventNotifierService
57+
.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(newTransaction));
58+
} else {
59+
businessEventNotifierService
60+
.notifyPostBusinessEvent(new LoanAccrualAdjustmentTransactionBusinessEvent(newTransaction));
61+
}
62+
}
5063
}
5164
businessEventNotifierService.stopExternalEventRecording();
5265
} catch (Exception e) {

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java

+6
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,23 @@
1919
package org.apache.fineract.portfolio.loanaccount.service;
2020

2121
import java.time.LocalDate;
22+
import java.util.List;
2223
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
2324
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
25+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
2426

2527
public interface ReprocessLoanTransactionsService {
2628

2729
void reprocessTransactions(Loan loan);
2830

31+
void reprocessTransactions(Loan loan, List<LoanTransaction> loanTransactions);
32+
2933
void reprocessTransactionsWithPostTransactionChecks(Loan loan, LocalDate transactionDate);
3034

3135
void processPostDisbursementTransactions(Loan loan);
3236

3337
void removeLoanCharge(Loan loan, LoanCharge loanCharge);
3438

39+
void processLatestTransaction(LoanTransaction loanTransaction, Loan loan);
40+
3541
}

0 commit comments

Comments
 (0)