FINERACT-2060: Progressive loan - final accrual calculation on closed loan

This commit is contained in:
Marta Jankovics
2024-12-13 16:48:07 +01:00
committed by Arnold Gálovics
parent c4cdd6f51a
commit 136bb0009a
34 changed files with 682 additions and 837 deletions
@@ -34,4 +34,5 @@ public class AccrualChargeData {
private Money chargeAmount;
private Money chargeAccruable;
private Money chargeAccrued;
private Money transactionAccrued;
}
@@ -68,10 +68,20 @@ public class AccrualPeriodData {
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
}
public Money getFeeTransactionAccrued() {
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getTransactionAccrued).reduce(null,
MathUtil::plus);
}
public Money getPenaltyAccrued() {
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
}
public Money getPenaltyTransactionAccrued() {
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getTransactionAccrued).reduce(null,
MathUtil::plus);
}
public Money getChargeAccruable() {
return charges.stream().map(AccrualChargeData::getChargeAccruable).reduce(null, MathUtil::plus);
}
@@ -1,201 +0,0 @@
/**
* 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 java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Map;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
public class LoanScheduleAccrualData {
private final Long loanId;
private final Long officeId;
private final LocalDate accruedTill;
private final PeriodFrequencyType repaymentFrequency;
private final Integer repayEvery;
private final Integer installmentNumber;
private final LocalDate dueDate;
private final LocalDate fromDate;
private final Long repaymentScheduleId;
private final Long loanProductId;
private final BigDecimal interestIncome;
private final BigDecimal feeIncome;
private final BigDecimal penaltyIncome;
private final BigDecimal waivedInterestIncome;
private final BigDecimal accruedInterestIncome;
private final BigDecimal accruedFeeIncome;
private final BigDecimal accruedPenaltyIncome;
private final CurrencyData currencyData;
private final LocalDate interestCalculatedFrom;
private Map<LoanChargeData, BigDecimal> applicableCharges;
private BigDecimal dueDateFeeIncome;
private BigDecimal dueDatePenaltyIncome;
private BigDecimal accruableIncome;
private BigDecimal creditedFee;
private BigDecimal creditedPenalty;
public LoanScheduleAccrualData(final Long loanId, final Long officeId, final Integer installmentNumber, final LocalDate accruedTill,
final PeriodFrequencyType repaymentFrequency, final Integer repayEvery, final LocalDate dueDate, final LocalDate fromDate,
final Long repaymentScheduleId, final Long loanProductId, final BigDecimal interestIncome, final BigDecimal feeIncome,
final BigDecimal penaltyIncome, final BigDecimal accruedInterestIncome, final BigDecimal accruedFeeIncome,
final BigDecimal accruedPenaltyIncome, final CurrencyData currencyData, final LocalDate interestCalculatedFrom,
final BigDecimal waivedInterestIncome, BigDecimal creditedFee, BigDecimal creditedPenalty) {
this.loanId = loanId;
this.installmentNumber = installmentNumber;
this.officeId = officeId;
this.accruedTill = accruedTill;
this.dueDate = dueDate;
this.fromDate = fromDate;
this.repaymentScheduleId = repaymentScheduleId;
this.loanProductId = loanProductId;
this.interestIncome = interestIncome;
this.feeIncome = feeIncome;
this.penaltyIncome = penaltyIncome;
this.accruedFeeIncome = accruedFeeIncome;
this.accruedInterestIncome = accruedInterestIncome;
this.accruedPenaltyIncome = accruedPenaltyIncome;
this.currencyData = currencyData;
this.repaymentFrequency = repaymentFrequency;
this.repayEvery = repayEvery;
this.interestCalculatedFrom = interestCalculatedFrom;
this.waivedInterestIncome = waivedInterestIncome;
this.creditedFee = creditedFee;
this.creditedPenalty = creditedPenalty;
}
public Long getLoanId() {
return this.loanId;
}
public Long getOfficeId() {
return this.officeId;
}
public LocalDate getDueDate() {
return this.dueDate;
}
public LocalDate getDueDateAsLocaldate() {
return this.dueDate;
}
public Long getRepaymentScheduleId() {
return this.repaymentScheduleId;
}
public Long getLoanProductId() {
return this.loanProductId;
}
public BigDecimal getInterestIncome() {
return this.interestIncome;
}
public BigDecimal getFeeIncome() {
return this.feeIncome;
}
public BigDecimal getPenaltyIncome() {
return this.penaltyIncome;
}
public BigDecimal getAccruedInterestIncome() {
return this.accruedInterestIncome;
}
public BigDecimal getAccruedFeeIncome() {
return this.accruedFeeIncome;
}
public BigDecimal getAccruedPenaltyIncome() {
return this.accruedPenaltyIncome;
}
public CurrencyData getCurrencyData() {
return this.currencyData;
}
public LocalDate getAccruedTill() {
return this.accruedTill;
}
public LocalDate getFromDateAsLocaldate() {
return this.fromDate;
}
public PeriodFrequencyType getRepaymentFrequency() {
return this.repaymentFrequency;
}
public Integer getRepayEvery() {
return this.repayEvery;
}
public LocalDate getInterestCalculatedFrom() {
return this.interestCalculatedFrom;
}
public Integer getInstallmentNumber() {
return this.installmentNumber;
}
public Map<LoanChargeData, BigDecimal> getApplicableCharges() {
return this.applicableCharges;
}
public BigDecimal getDueDateFeeIncome() {
return this.dueDateFeeIncome;
}
public BigDecimal getDueDatePenaltyIncome() {
return this.dueDatePenaltyIncome;
}
public void updateChargeDetails(final Map<LoanChargeData, BigDecimal> applicableCharges, final BigDecimal dueDateFeeIncome,
final BigDecimal dueDatePenaltyIncome) {
this.applicableCharges = applicableCharges;
this.dueDateFeeIncome = dueDateFeeIncome;
this.dueDatePenaltyIncome = dueDatePenaltyIncome;
}
public BigDecimal getWaivedInterestIncome() {
return this.waivedInterestIncome;
}
public BigDecimal getAccruableIncome() {
return this.accruableIncome;
}
public void updateAccruableIncome(BigDecimal accruableIncome) {
this.accruableIncome = accruableIncome;
}
public BigDecimal getCreditedFee() {
return this.creditedFee;
}
public BigDecimal getCreditedPenalty() {
return this.creditedPenalty;
}
}
@@ -18,6 +18,8 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain;
import static org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType.PROGRESSIVE;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
@@ -740,6 +742,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
* is lesser than todays date. If not, the transaction date is set to todays date
*/
public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) {
if (isProgressiveSchedule()) {
return null;
}
final Money chargeAmount = loanCharge.getAmount(getCurrency());
Money feeCharges = chargeAmount;
Money penaltyCharges = Money.zero(getCurrency());
@@ -1053,7 +1058,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
LoanCharge charge = loanCharge;
// add new charges
if (loanCharge.getId() == null) {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge;
loanCharge.update(this);
if (this.loanProduct.isMultiDisburseLoan() && loanCharge.isTrancheDisbursementCharge()) {
loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().updateLoan(this);
@@ -3563,4 +3568,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
public LoanRepaymentScheduleTransactionProcessor getTransactionProcessor() {
return transactionProcessorFactory.determineProcessor(transactionProcessingStrategyCode);
}
public boolean isProgressiveSchedule() {
return getLoanProductRelatedDetail().getLoanScheduleType() == PROGRESSIVE;
}
}
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.springframework.transaction.annotation.Transactional;
@@ -25,13 +26,13 @@ import org.springframework.transaction.annotation.Transactional;
public interface LoanAccrualActivityProcessingService {
@Transactional
void makeAccrualActivityTransaction(Long loanId, LocalDate currentDate);
void makeAccrualActivityTransaction(@NotNull Long loanId, @NotNull LocalDate currentDate);
Loan makeAccrualActivityTransaction(Loan loan, LocalDate currentDate);
void makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LocalDate currentDate);
@Transactional
void processAccrualActivityForLoanClosure(Loan loan);
void processAccrualActivityForLoanClosure(@NotNull Loan loan);
@Transactional
void processAccrualActivityForLoanReopen(Loan loan);
void processAccrualActivityForLoanReopen(@NotNull Loan loan);
}
@@ -20,7 +20,9 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.util.List;
import lombok.RequiredArgsConstructor;
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.domain.loan.transaction.LoanTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -33,8 +35,12 @@ public class LoanAccrualTransactionBusinessEventServiceImpl implements LoanAccru
@Override
public void raiseBusinessEventForAccrualTransactions(Loan loan, List<Long> existingTransactionIds) {
for (final LoanTransaction transaction : loan.getLoanTransactions()) {
if (transaction.isNotReversed() && transaction.isAccrual() && !existingTransactionIds.contains(transaction.getId())) {
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(transaction));
if (transaction.isNotReversed() && (transaction.isAccrual() || transaction.isAccrualAdjustment())
&& !existingTransactionIds.contains(transaction.getId())) {
LoanTransactionBusinessEvent businessEvent = transaction.isAccrual()
? new LoanAccrualTransactionCreatedBusinessEvent(transaction)
: new LoanAccrualAdjustmentTransactionBusinessEvent(transaction);
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
}
}
}
@@ -35,16 +35,14 @@ public interface LoanAccrualsProcessingService {
void reprocessExistingAccruals(@NotNull Loan loan);
void processAccrualsForInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled);
void processAccrualsOnInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled, boolean addJournal);
void addIncomePostingAndAccruals(Long loanId) throws Exception;
void processIncomePostingAndAccruals(@NotNull Loan loan);
void addIncomeAndAccrualTransactions(Long loanId) throws Exception;
void processAccrualsOnLoanClosure(@NotNull Loan loan);
void processAccrualsOnLoanReopen(@NotNull Loan loan);
void processAccrualsOnLoanForeClosure(@NotNull Loan loan, @NotNull LocalDate foreClosureDate,
@NotNull List<LoanTransaction> newAccrualTransactions);
}
@@ -30,7 +30,6 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.springframework.transaction.annotation.Transactional;
@@ -53,16 +52,6 @@ public interface LoanWritePlatformService {
CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(LoanTransactionType repaymentTransactionType, Long loanId,
JsonCommand command, boolean isRecoveryRepayment, String chargeRefundChargeType);
@Transactional
Loan reverseReplayAccrualActivityTransaction(Loan loan, LoanTransaction loanTransaction, LoanRepaymentScheduleInstallment installment,
LocalDate transactionDate);
@Transactional
Loan makeAccrualActivityTransaction(Loan loan, LoanRepaymentScheduleInstallment installment, LocalDate transactionDate);
@Transactional
Loan makeAccrualActivityTransaction(Loan loan, LoanTransaction accrualActivityTransaction);
@Transactional
CommandProcessingResult makeInterestPaymentWaiver(JsonCommand command);