|
26 | 26 | import java.util.Map; |
27 | 27 | import java.util.Optional; |
28 | 28 | import java.util.Set; |
| 29 | +import java.util.concurrent.atomic.AtomicBoolean; |
29 | 30 | import java.util.stream.Collectors; |
30 | 31 | import lombok.RequiredArgsConstructor; |
31 | 32 | import lombok.extern.slf4j.Slf4j; |
|
36 | 37 | import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent; |
37 | 38 | import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent; |
38 | 39 | import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; |
| 40 | +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; |
| 41 | +import org.apache.fineract.organisation.monetary.domain.Money; |
| 42 | +import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData; |
| 43 | +import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; |
39 | 44 | import org.apache.fineract.portfolio.loanaccount.domain.Loan; |
40 | 45 | import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; |
41 | 46 | import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; |
| 47 | +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper; |
42 | 48 | import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; |
43 | 49 | import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; |
44 | 50 | import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation; |
@@ -101,6 +107,32 @@ public void makeAccrualActivityTransaction(final @NonNull Loan loan, final @NonN |
101 | 107 | }); |
102 | 108 | } |
103 | 109 |
|
| 110 | + @Override |
| 111 | + public void recalculateAccrualActivityTransaction(Loan loan, ChangedTransactionDetail changedTransactionDetail) { |
| 112 | + List<LoanTransaction> accrualActivities = loanTransactionRepository.findNonReversedByLoanAndType(loan, |
| 113 | + LoanTransactionType.ACCRUAL_ACTIVITY); |
| 114 | + accrualActivities.forEach(accrualActivity -> { |
| 115 | + final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(accrualActivity); |
| 116 | + |
| 117 | + calculateAccrualActivity(newLoanTransaction, loan.getCurrency(), loan.getRepaymentScheduleInstallments()); |
| 118 | + |
| 119 | + if (!LoanTransaction.transactionAmountsMatch(loan.getCurrency(), accrualActivity, newLoanTransaction)) { |
| 120 | + createNewTransaction(accrualActivity, newLoanTransaction, changedTransactionDetail); |
| 121 | + } |
| 122 | + }); |
| 123 | + } |
| 124 | + |
| 125 | + protected void createNewTransaction(LoanTransaction loanTransaction, LoanTransaction newLoanTransaction, |
| 126 | + ChangedTransactionDetail changedTransactionDetail) { |
| 127 | + loanTransaction.reverse(); |
| 128 | + loanTransaction.updateExternalId(null); |
| 129 | + newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations()); |
| 130 | + // Adding Replayed relation from newly created transaction to reversed transaction |
| 131 | + newLoanTransaction.getLoanTransactionRelations().add( |
| 132 | + LoanTransactionRelation.linkToTransaction(newLoanTransaction, loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED)); |
| 133 | + changedTransactionDetail.addTransactionChange(new TransactionChangeData(loanTransaction, newLoanTransaction)); |
| 134 | + } |
| 135 | + |
104 | 136 | @Override |
105 | 137 | @Transactional |
106 | 138 | public void processAccrualActivityForLoanClosure(final @NonNull Loan loan) { |
@@ -206,6 +238,52 @@ public void processAccrualActivityForLoanReopen(final @NonNull Loan loan) { |
206 | 238 | } |
207 | 239 | } |
208 | 240 |
|
| 241 | + private void calculateAccrualActivity(LoanTransaction loanTransaction, MonetaryCurrency currency, |
| 242 | + List<LoanRepaymentScheduleInstallment> installments) { |
| 243 | + |
| 244 | + final int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments); |
| 245 | + |
| 246 | + final List<LoanRepaymentScheduleInstallment> targetInstallments = installments.stream() |
| 247 | + .filter(installment -> LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(), installment, |
| 248 | + installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)) |
| 249 | + || (DateUtils.isEqual(installment.getObligationsMetOnDate(), loanTransaction.getTransactionDate()) |
| 250 | + && installment.getDueDate().isAfter(loanTransaction.getTransactionDate()))) |
| 251 | + .toList(); |
| 252 | + |
| 253 | + if (targetInstallments.isEmpty()) { |
| 254 | + return; |
| 255 | + } |
| 256 | + |
| 257 | + AtomicBoolean isReset = new AtomicBoolean(false); |
| 258 | + targetInstallments.forEach(currentInstallment -> { |
| 259 | + if (currentInstallment.isNotFullyPaidOff() && (currentInstallment.getDueDate().isAfter(loanTransaction.getTransactionDate()) |
| 260 | + || (currentInstallment.getDueDate().isEqual(loanTransaction.getTransactionDate()) |
| 261 | + && loanTransaction.getTransactionDate().equals(DateUtils.getBusinessLocalDate())))) { |
| 262 | + loanTransaction.reverse(); |
| 263 | + } else { |
| 264 | + if (!isReset.get()) { |
| 265 | + loanTransaction.resetDerivedComponents(); |
| 266 | + isReset.set(true); |
| 267 | + } |
| 268 | + final Money principalPortion = Money.zero(currency); |
| 269 | + Money interestPortion = currentInstallment.getInterestCharged(currency); |
| 270 | + Money feeChargesPortion = currentInstallment.getFeeChargesCharged(currency); |
| 271 | + Money penaltyChargesPortion = currentInstallment.getPenaltyChargesCharged(currency); |
| 272 | + |
| 273 | + loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); |
| 274 | + final Loan loan = loanTransaction.getLoan(); |
| 275 | + if ((loan.isClosedObligationsMet() || loanBalanceService.isOverPaid(loan)) && currentInstallment.isObligationsMet() |
| 276 | + && currentInstallment.isTransactionDateWithinPeriod(currentInstallment.getObligationsMetOnDate())) { |
| 277 | + loanTransaction.updateTransactionDate(currentInstallment.getObligationsMetOnDate()); |
| 278 | + } |
| 279 | + } |
| 280 | + }); |
| 281 | + if (MathUtil.isZero(MathUtil.nullToZero(MathUtil.add(loanTransaction.getInterestPortion(), loanTransaction.getFeeChargesPortion(), |
| 282 | + loanTransaction.getPenaltyChargesPortion())))) { |
| 283 | + loanTransaction.reverse(); |
| 284 | + } |
| 285 | + } |
| 286 | + |
209 | 287 | private Map<LocalDate, List<LoanTransaction>> loadExistingAccrualActivitiesByDate(final @NonNull Loan loan, |
210 | 288 | final List<LoanRepaymentScheduleInstallment> installments) { |
211 | 289 | final Set<LocalDate> dueDates = installments.stream().map(LoanRepaymentScheduleInstallment::getDueDate).collect(Collectors.toSet()); |
|
0 commit comments