FINERACT-2090: Accruals rework
This commit is contained in:
committed by
Adam Saghy
parent
3f4bbf3e14
commit
1b75c868ba
+2
-414
@@ -1260,7 +1260,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
updateLoanSummaryDerivedFields();
|
||||
applyAccruals();
|
||||
}
|
||||
|
||||
public void updateLoanSchedule(final Collection<LoanRepaymentScheduleInstallment> installments) {
|
||||
@@ -1279,7 +1278,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
updateLoanSummaryDerivedFields();
|
||||
applyAccruals();
|
||||
|
||||
}
|
||||
|
||||
@@ -1293,94 +1291,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* method updates accrual derived fields on installments and reverse the unprocessed transactions
|
||||
*/
|
||||
private void applyAccruals() {
|
||||
Collection<LoanTransaction> accruals = retrieveListOfAccrualTransactions();
|
||||
if (!accruals.isEmpty()) {
|
||||
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
|
||||
applyPeriodicAccruals(accruals);
|
||||
} else if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
|
||||
updateAccrualsForNonPeriodicAccruals(accruals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyPeriodicAccruals(final Collection<LoanTransaction> accruals) {
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
boolean isBasedOnSubmittedOnDate = TemporaryConfigurationServiceContainer.getAccrualDateConfigForCharge()
|
||||
.equalsIgnoreCase("submitted-date");
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
Money interest = Money.zero(getCurrency());
|
||||
Money fee = Money.zero(getCurrency());
|
||||
Money penalty = Money.zero(getCurrency());
|
||||
for (LoanTransaction loanTransaction : accruals) {
|
||||
LocalDate transactionDateForRange = getDateForRangeCalculation(loanTransaction, isBasedOnSubmittedOnDate);
|
||||
boolean isInPeriod = LoanRepaymentScheduleProcessingWrapper.isInPeriod(transactionDateForRange, installment, installments);
|
||||
if (isInPeriod) {
|
||||
interest = interest.plus(loanTransaction.getInterestPortion(getCurrency()));
|
||||
fee = fee.plus(loanTransaction.getFeeChargesPortion(getCurrency()));
|
||||
penalty = penalty.plus(loanTransaction.getPenaltyChargesPortion(getCurrency()));
|
||||
if (installment.getFeeChargesCharged(getCurrency()).isLessThan(fee)
|
||||
|| installment.getInterestCharged(getCurrency()).isLessThan(interest)
|
||||
|| installment.getPenaltyChargesCharged(getCurrency()).isLessThan(penalty)
|
||||
|| (isInterestBearing() && DateUtils.isEqual(getAccruedTill(), loanTransaction.getTransactionDate())
|
||||
&& !DateUtils.isEqual(getAccruedTill(), installment.getDueDate()))) {
|
||||
interest = interest.minus(loanTransaction.getInterestPortion(getCurrency()));
|
||||
fee = fee.minus(loanTransaction.getFeeChargesPortion(getCurrency()));
|
||||
penalty = penalty.minus(loanTransaction.getPenaltyChargesPortion(getCurrency()));
|
||||
loanTransaction.reverse();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
installment.updateAccrualPortion(interest, fee, penalty);
|
||||
}
|
||||
LoanRepaymentScheduleInstallment lastInstallment = getLastLoanRepaymentScheduleInstallment();
|
||||
for (LoanTransaction loanTransaction : accruals) {
|
||||
if (!loanTransaction.isReversed() && DateUtils.isAfter(loanTransaction.getTransactionDate(), lastInstallment.getDueDate())) {
|
||||
loanTransaction.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate getDateForRangeCalculation(LoanTransaction loanTransaction, boolean isChargeAccrualBasedOnSubmittedOnDate) {
|
||||
return isChargeAccrualBasedOnSubmittedOnDate && !loanTransaction.getLoanChargesPaid().isEmpty()
|
||||
? loanTransaction.getLoanChargesPaid().stream().findFirst().get().getLoanCharge().getEffectiveDueDate()
|
||||
: loanTransaction.getTransactionDate();
|
||||
}
|
||||
|
||||
private void updateAccrualsForNonPeriodicAccruals(final Collection<LoanTransaction> accruals) {
|
||||
final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
|
||||
ExternalId externalId = ExternalId.empty();
|
||||
boolean isExternalIdAutoGenerationEnabled = TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled();
|
||||
|
||||
for (LoanTransaction loanTransaction : accruals) {
|
||||
if (loanTransaction.getInterestPortion(getCurrency()).isGreaterThanZero()) {
|
||||
if (loanTransaction.getInterestPortion(getCurrency()).isNotEqualTo(interestApplied)) {
|
||||
loanTransaction.reverse();
|
||||
if (isExternalIdAutoGenerationEnabled) {
|
||||
externalId = ExternalId.generate();
|
||||
}
|
||||
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
|
||||
getDisbursementDate(), externalId);
|
||||
addLoanTransaction(interestAppliedTransaction);
|
||||
}
|
||||
} else {
|
||||
Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid();
|
||||
for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
|
||||
LoanCharge loanCharge = chargePaidBy.getLoanCharge();
|
||||
Money chargeAmount = loanCharge.getAmount(getCurrency());
|
||||
if (chargeAmount.isNotEqualTo(loanTransaction.getAmount(getCurrency()))) {
|
||||
loanTransaction.reverse();
|
||||
handleChargeAppliedTransaction(loanCharge, loanTransaction.getTransactionDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateLoanScheduleDependentDerivedFields() {
|
||||
if (this.getLoanRepaymentScheduleInstallmentsSize() > 0) {
|
||||
this.expectedMaturityDate = determineExpectedMaturityDate();
|
||||
@@ -2061,7 +1971,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return details;
|
||||
}
|
||||
|
||||
private boolean isDisbursementMissed() {
|
||||
public boolean isDisbursementMissed() {
|
||||
return getDisbursementDetails().stream() //
|
||||
.anyMatch(disbursementDetail -> disbursementDetail.actualDisbursementDate() == null
|
||||
&& DateUtils.isBeforeBusinessDate(disbursementDetail.expectedDisbursementDateAsLocalDate()));
|
||||
@@ -2683,12 +2593,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private List<LoanTransaction> retrieveListOfIncomePostingTransactions() {
|
||||
return getLoanTransactions().stream() //
|
||||
.filter(transaction -> transaction.isNotReversed() && transaction.isIncomePosting()) //
|
||||
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<LoanTransaction> retrieveListOfTransactionsForReprocessing() {
|
||||
return getLoanTransactions().stream()
|
||||
.filter(transaction -> transaction.isNotReversed() && !transaction.isAccrual()
|
||||
@@ -2715,11 +2619,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return repaymentsOrWaivers;
|
||||
}
|
||||
|
||||
private List<LoanTransaction> retrieveListOfAccrualTransactions() {
|
||||
return this.loanTransactions.stream().filter(transaction -> transaction.isNotReversed() && transaction.isAccrual())
|
||||
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<LoanTransaction> retrieveListOfTransactionsByType(final LoanTransactionType transactionType) {
|
||||
return this.loanTransactions.stream()
|
||||
.filter(transaction -> transaction.isNotReversed() && transaction.getTypeOf().equals(transactionType))
|
||||
@@ -2762,82 +2661,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, this);
|
||||
}
|
||||
processIncomeAccrualTransactionOnLoanClosure();
|
||||
}
|
||||
|
||||
public void processIncomeAccrualTransactionOnLoanClosure() {
|
||||
if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()
|
||||
&& this.getStatus().isClosedObligationsMet() && !isNpa() && !isChargedOff()) {
|
||||
|
||||
ExternalId externalId = ExternalId.empty();
|
||||
boolean isExternalIdAutoGenerationEnabled = TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled();
|
||||
|
||||
LocalDate closedDate = this.getClosedOnDate();
|
||||
reverseTransactionsOnOrAfter(retrieveListOfIncomePostingTransactions(), closedDate);
|
||||
reverseTransactionsOnOrAfter(retrieveListOfAccrualTransactions(), closedDate);
|
||||
HashMap<String, BigDecimal> cumulativeIncomeFromInstallments = new HashMap<>();
|
||||
determineCumulativeIncomeFromInstallments(cumulativeIncomeFromInstallments);
|
||||
HashMap<String, BigDecimal> cumulativeIncomeFromIncomePosting = new HashMap<>();
|
||||
determineCumulativeIncomeDetails(retrieveListOfIncomePostingTransactions(), cumulativeIncomeFromIncomePosting);
|
||||
BigDecimal interestToPost = cumulativeIncomeFromInstallments.get(INTEREST)
|
||||
.subtract(cumulativeIncomeFromIncomePosting.get(INTEREST));
|
||||
BigDecimal feeToPost = cumulativeIncomeFromInstallments.get(FEE).subtract(cumulativeIncomeFromIncomePosting.get(FEE));
|
||||
BigDecimal penaltyToPost = cumulativeIncomeFromInstallments.get(PENALTY)
|
||||
.subtract(cumulativeIncomeFromIncomePosting.get(PENALTY));
|
||||
BigDecimal amountToPost = interestToPost.add(feeToPost).add(penaltyToPost);
|
||||
if (isExternalIdAutoGenerationEnabled) {
|
||||
externalId = ExternalId.generate();
|
||||
}
|
||||
LoanTransaction finalIncomeTransaction = LoanTransaction.incomePosting(this, this.getOffice(), closedDate, amountToPost,
|
||||
interestToPost, feeToPost, penaltyToPost, externalId);
|
||||
addLoanTransaction(finalIncomeTransaction);
|
||||
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
|
||||
List<LoanTransaction> updatedAccrualTransactions = retrieveListOfAccrualTransactions();
|
||||
LocalDate lastAccruedDate = this.getDisbursementDate();
|
||||
if (!updatedAccrualTransactions.isEmpty()) {
|
||||
lastAccruedDate = updatedAccrualTransactions.get(updatedAccrualTransactions.size() - 1).getTransactionDate();
|
||||
}
|
||||
HashMap<String, Object> feeDetails = new HashMap<>();
|
||||
determineFeeDetails(lastAccruedDate, closedDate, feeDetails);
|
||||
if (isExternalIdAutoGenerationEnabled) {
|
||||
externalId = ExternalId.generate();
|
||||
}
|
||||
LoanTransaction finalAccrual = LoanTransaction.accrueTransaction(this, this.getOffice(), closedDate, amountToPost,
|
||||
interestToPost, feeToPost, penaltyToPost, externalId);
|
||||
updateLoanChargesPaidBy(finalAccrual, feeDetails, null);
|
||||
addLoanTransaction(finalAccrual);
|
||||
}
|
||||
}
|
||||
updateLoanOutstandingBalances();
|
||||
}
|
||||
|
||||
private void determineCumulativeIncomeFromInstallments(HashMap<String, BigDecimal> cumulativeIncomeFromInstallments) {
|
||||
BigDecimal interest = BigDecimal.ZERO;
|
||||
BigDecimal fee = BigDecimal.ZERO;
|
||||
BigDecimal penalty = BigDecimal.ZERO;
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
interest = interest.add(installment.getInterestCharged(getCurrency()).getAmount());
|
||||
fee = fee.add(installment.getFeeChargesCharged(getCurrency()).getAmount());
|
||||
penalty = penalty.add(installment.getPenaltyChargesCharged(getCurrency()).getAmount());
|
||||
}
|
||||
cumulativeIncomeFromInstallments.put(INTEREST, interest);
|
||||
cumulativeIncomeFromInstallments.put(FEE, fee);
|
||||
cumulativeIncomeFromInstallments.put(PENALTY, penalty);
|
||||
}
|
||||
|
||||
private void determineCumulativeIncomeDetails(Collection<LoanTransaction> transactions, HashMap<String, BigDecimal> incomeDetailsMap) {
|
||||
BigDecimal interest = BigDecimal.ZERO;
|
||||
BigDecimal fee = BigDecimal.ZERO;
|
||||
BigDecimal penalty = BigDecimal.ZERO;
|
||||
for (LoanTransaction transaction : transactions) {
|
||||
interest = interest.add(transaction.getInterestPortion(getCurrency()).getAmount());
|
||||
fee = fee.add(transaction.getFeeChargesPortion(getCurrency()).getAmount());
|
||||
penalty = penalty.add(transaction.getPenaltyChargesPortion(getCurrency()).getAmount());
|
||||
}
|
||||
incomeDetailsMap.put(INTEREST, interest);
|
||||
incomeDetailsMap.put(FEE, fee);
|
||||
incomeDetailsMap.put(PENALTY, penalty);
|
||||
}
|
||||
|
||||
private void handleLoanOverpayment(LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||
@@ -4646,197 +4469,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
processPostDisbursementTransactions();
|
||||
processIncomeTransactions();
|
||||
}
|
||||
|
||||
private void updateLoanChargesPaidBy(LoanTransaction accrual, Map<String, Object> feeDetails,
|
||||
LoanRepaymentScheduleInstallment installment) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<LoanCharge> loanCharges = (List<LoanCharge>) feeDetails.get("loanCharges");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<LoanInstallmentCharge> loanInstallmentCharges = (List<LoanInstallmentCharge>) feeDetails.get("loanInstallmentCharges");
|
||||
if (loanCharges != null) {
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
Integer installmentNumber = null == installment ? null : installment.getInstallmentNumber();
|
||||
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrual, loanCharge,
|
||||
loanCharge.getAmount(getCurrency()).getAmount(), installmentNumber);
|
||||
accrual.getLoanChargesPaid().add(loanChargePaidBy);
|
||||
}
|
||||
}
|
||||
if (loanInstallmentCharges != null) {
|
||||
for (LoanInstallmentCharge loanInstallmentCharge : loanInstallmentCharges) {
|
||||
Integer installmentNumber = null == loanInstallmentCharge.getInstallment() ? null
|
||||
: loanInstallmentCharge.getInstallment().getInstallmentNumber();
|
||||
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrual, loanInstallmentCharge.getLoanCharge(),
|
||||
loanInstallmentCharge.getAmount(getCurrency()).getAmount(), installmentNumber);
|
||||
accrual.getLoanChargesPaid().add(loanChargePaidBy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void processIncomeTransactions() {
|
||||
if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()) {
|
||||
LocalDate lastCompoundingDate = this.getDisbursementDate();
|
||||
List<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = extractInterestRecalculationAdditionalDetails();
|
||||
List<LoanTransaction> incomeTransactions = retrieveListOfIncomePostingTransactions();
|
||||
List<LoanTransaction> accrualTransactions = retrieveListOfAccrualTransactions();
|
||||
for (LoanInterestRecalcualtionAdditionalDetails compoundingDetail : compoundingDetails) {
|
||||
if (!DateUtils.isBeforeBusinessDate(compoundingDetail.getEffectiveDate())) {
|
||||
break;
|
||||
}
|
||||
LoanTransaction incomeTransaction = getTransactionForDate(incomeTransactions, compoundingDetail.getEffectiveDate());
|
||||
LoanTransaction accrualTransaction = getTransactionForDate(accrualTransactions, compoundingDetail.getEffectiveDate());
|
||||
addUpdateIncomeAndAccrualTransaction(compoundingDetail, lastCompoundingDate, incomeTransaction, accrualTransaction);
|
||||
lastCompoundingDate = compoundingDetail.getEffectiveDate();
|
||||
}
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
LoanRepaymentScheduleInstallment lastInstallment = LoanRepaymentScheduleInstallment
|
||||
.getLastNonDownPaymentInstallment(installments);
|
||||
reverseTransactionsPostEffectiveDate(incomeTransactions, lastInstallment.getDueDate());
|
||||
reverseTransactionsPostEffectiveDate(accrualTransactions, lastInstallment.getDueDate());
|
||||
}
|
||||
}
|
||||
|
||||
private void reverseTransactionsOnOrAfter(List<LoanTransaction> transactions, LocalDate date) {
|
||||
for (LoanTransaction loanTransaction : transactions) {
|
||||
if (!DateUtils.isBefore(loanTransaction.getTransactionDate(), date)) {
|
||||
loanTransaction.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addUpdateIncomeAndAccrualTransaction(LoanInterestRecalcualtionAdditionalDetails compoundingDetail,
|
||||
LocalDate lastCompoundingDate, LoanTransaction existingIncomeTransaction, LoanTransaction existingAccrualTransaction) {
|
||||
BigDecimal interest = BigDecimal.ZERO;
|
||||
BigDecimal fee = BigDecimal.ZERO;
|
||||
BigDecimal penalties = BigDecimal.ZERO;
|
||||
HashMap<String, Object> feeDetails = new HashMap<>();
|
||||
|
||||
if (this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod()
|
||||
.equals(InterestRecalculationCompoundingMethod.INTEREST)) {
|
||||
interest = compoundingDetail.getAmount();
|
||||
} else if (this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod()
|
||||
.equals(InterestRecalculationCompoundingMethod.FEE)) {
|
||||
determineFeeDetails(lastCompoundingDate, compoundingDetail.getEffectiveDate(), feeDetails);
|
||||
fee = (BigDecimal) feeDetails.get(FEE);
|
||||
penalties = (BigDecimal) feeDetails.get(PENALTIES);
|
||||
} else if (this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod()
|
||||
.equals(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE)) {
|
||||
determineFeeDetails(lastCompoundingDate, compoundingDetail.getEffectiveDate(), feeDetails);
|
||||
fee = (BigDecimal) feeDetails.get(FEE);
|
||||
penalties = (BigDecimal) feeDetails.get(PENALTIES);
|
||||
interest = compoundingDetail.getAmount().subtract(fee).subtract(penalties);
|
||||
}
|
||||
|
||||
ExternalId externalId = ExternalId.empty();
|
||||
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
|
||||
externalId = ExternalId.generate();
|
||||
}
|
||||
|
||||
if (existingIncomeTransaction == null) {
|
||||
LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
|
||||
compoundingDetail.getAmount(), interest, fee, penalties, externalId);
|
||||
addLoanTransaction(transaction);
|
||||
} else if (existingIncomeTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) {
|
||||
existingIncomeTransaction.reverse();
|
||||
LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
|
||||
compoundingDetail.getAmount(), interest, fee, penalties, externalId);
|
||||
addLoanTransaction(transaction);
|
||||
}
|
||||
|
||||
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
|
||||
externalId = ExternalId.generate();
|
||||
}
|
||||
|
||||
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
|
||||
if (existingAccrualTransaction == null) {
|
||||
LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
|
||||
compoundingDetail.getAmount(), interest, fee, penalties, externalId);
|
||||
updateLoanChargesPaidBy(accrual, feeDetails, null);
|
||||
addLoanTransaction(accrual);
|
||||
} else if (existingAccrualTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) {
|
||||
existingAccrualTransaction.reverse();
|
||||
LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
|
||||
compoundingDetail.getAmount(), interest, fee, penalties, externalId);
|
||||
updateLoanChargesPaidBy(accrual, feeDetails, null);
|
||||
addLoanTransaction(accrual);
|
||||
}
|
||||
}
|
||||
updateLoanOutstandingBalances();
|
||||
}
|
||||
|
||||
private void determineFeeDetails(LocalDate fromDate, LocalDate toDate, Map<String, Object> feeDetails) {
|
||||
BigDecimal fee = BigDecimal.ZERO;
|
||||
BigDecimal penalties = BigDecimal.ZERO;
|
||||
|
||||
List<Integer> installments = new ArrayList<>();
|
||||
List<LoanRepaymentScheduleInstallment> repaymentSchedule = getRepaymentScheduleInstallments();
|
||||
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : repaymentSchedule) {
|
||||
if (DateUtils.isAfter(loanRepaymentScheduleInstallment.getDueDate(), fromDate)
|
||||
&& !DateUtils.isAfter(loanRepaymentScheduleInstallment.getDueDate(), toDate)) {
|
||||
installments.add(loanRepaymentScheduleInstallment.getInstallmentNumber());
|
||||
}
|
||||
}
|
||||
|
||||
List<LoanCharge> loanCharges = new ArrayList<>();
|
||||
List<LoanInstallmentCharge> loanInstallmentCharges = new ArrayList<>();
|
||||
for (LoanCharge loanCharge : this.getActiveCharges()) {
|
||||
boolean isDue = DateUtils.isEqual(fromDate, this.getDisbursementDate())
|
||||
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(fromDate, toDate)
|
||||
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, toDate);
|
||||
if (isDue) {
|
||||
if (loanCharge.isPenaltyCharge() && !loanCharge.isInstalmentFee()) {
|
||||
penalties = penalties.add(loanCharge.amount());
|
||||
loanCharges.add(loanCharge);
|
||||
} else if (!loanCharge.isInstalmentFee()) {
|
||||
fee = fee.add(loanCharge.amount());
|
||||
loanCharges.add(loanCharge);
|
||||
}
|
||||
} else if (loanCharge.isInstalmentFee()) {
|
||||
for (LoanInstallmentCharge installmentCharge : loanCharge.installmentCharges()) {
|
||||
if (installments.contains(installmentCharge.getRepaymentInstallment().getInstallmentNumber())) {
|
||||
fee = fee.add(installmentCharge.getAmount());
|
||||
loanInstallmentCharges.add(installmentCharge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
feeDetails.put(FEE, fee);
|
||||
feeDetails.put(PENALTIES, penalties);
|
||||
feeDetails.put("loanCharges", loanCharges);
|
||||
feeDetails.put("loanInstallmentCharges", loanInstallmentCharges);
|
||||
}
|
||||
|
||||
private LoanTransaction getTransactionForDate(List<LoanTransaction> transactions, LocalDate effectiveDate) {
|
||||
for (LoanTransaction loanTransaction : transactions) {
|
||||
if (DateUtils.isEqual(effectiveDate, loanTransaction.getTransactionDate())) {
|
||||
return loanTransaction;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void reverseTransactionsPostEffectiveDate(List<LoanTransaction> transactions, LocalDate effectiveDate) {
|
||||
for (LoanTransaction loanTransaction : transactions) {
|
||||
if (DateUtils.isAfter(loanTransaction.getTransactionDate(), effectiveDate)) {
|
||||
loanTransaction.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<LoanInterestRecalcualtionAdditionalDetails> extractInterestRecalculationAdditionalDetails() {
|
||||
List<LoanInterestRecalcualtionAdditionalDetails> retDetails = new ArrayList<>();
|
||||
List<LoanRepaymentScheduleInstallment> repaymentSchedule = getRepaymentScheduleInstallments();
|
||||
if (null != repaymentSchedule) {
|
||||
for (LoanRepaymentScheduleInstallment installment : repaymentSchedule) {
|
||||
if (null != installment.getLoanCompoundingDetails()) {
|
||||
retDetails.addAll(installment.getLoanCompoundingDetails());
|
||||
}
|
||||
}
|
||||
}
|
||||
retDetails.sort(Comparator.comparing(LoanInterestRecalcualtionAdditionalDetails::getEffectiveDate));
|
||||
return retDetails;
|
||||
}
|
||||
|
||||
public void processPostDisbursementTransactions() {
|
||||
@@ -4996,7 +4628,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return recalculatedOn;
|
||||
}
|
||||
|
||||
private void updateLoanOutstandingBalances() {
|
||||
public void updateLoanOutstandingBalances() {
|
||||
Money outstanding = Money.zero(getCurrency());
|
||||
List<LoanTransaction> loanTransactions = retrieveListOfTransactionsExcludeAccruals();
|
||||
for (LoanTransaction loanTransaction : loanTransactions) {
|
||||
@@ -5755,56 +5387,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return interest.doubleValue() / daysInPeriod * days;
|
||||
}
|
||||
|
||||
public Money[] getReceivableIncome(final LocalDate tillDate) {
|
||||
MonetaryCurrency currency = getCurrency();
|
||||
Money receivableInterest = Money.zero(currency);
|
||||
Money receivableFee = Money.zero(currency);
|
||||
Money receivablePenalty = Money.zero(currency);
|
||||
Money[] receivables = new Money[3];
|
||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||
if (transaction.isNotReversed() && !transaction.isRepaymentAtDisbursement() && !transaction.isDisbursement()
|
||||
&& !DateUtils.isAfter(transaction.getTransactionDate(), tillDate)) {
|
||||
if (transaction.isAccrual()) {
|
||||
receivableInterest = receivableInterest.plus(transaction.getInterestPortion(currency));
|
||||
receivableFee = receivableFee.plus(transaction.getFeeChargesPortion(currency));
|
||||
receivablePenalty = receivablePenalty.plus(transaction.getPenaltyChargesPortion(currency));
|
||||
} else if (transaction.isRepaymentLikeType() || transaction.isChargePayment()) {
|
||||
receivableInterest = receivableInterest.minus(transaction.getInterestPortion(currency));
|
||||
receivableFee = receivableFee.minus(transaction.getFeeChargesPortion(currency));
|
||||
receivablePenalty = receivablePenalty.minus(transaction.getPenaltyChargesPortion(currency));
|
||||
}
|
||||
}
|
||||
if (receivableInterest.isLessThanZero()) {
|
||||
receivableInterest = receivableInterest.zero();
|
||||
}
|
||||
if (receivableFee.isLessThanZero()) {
|
||||
receivableFee = receivableFee.zero();
|
||||
}
|
||||
if (receivablePenalty.isLessThanZero()) {
|
||||
receivablePenalty = receivablePenalty.zero();
|
||||
}
|
||||
}
|
||||
receivables[0] = receivableInterest;
|
||||
receivables[1] = receivableFee;
|
||||
receivables[2] = receivablePenalty;
|
||||
return receivables;
|
||||
}
|
||||
|
||||
public void reverseAccrualsAfter(final LocalDate tillDate) {
|
||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||
if (transaction.isAccrual() && DateUtils.isAfter(transaction.getTransactionDate(), tillDate)) {
|
||||
transaction.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ChangedTransactionDetail handleForeClosureTransactions(final LoanTransaction repaymentTransaction,
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
LoanEvent event = LoanEvent.LOAN_FORECLOSURE;
|
||||
validateAccountStatus(event);
|
||||
validateForForeclosure(repaymentTransaction.getTransactionDate());
|
||||
this.loanSubStatus = LoanSubStatus.FORECLOSED.getValue();
|
||||
applyAccruals();
|
||||
return handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction, loanLifecycleStateMachine, null, scheduleGeneratorDTO);
|
||||
}
|
||||
|
||||
|
||||
-11
@@ -57,14 +57,6 @@ public interface LoanAccountDomainService {
|
||||
|
||||
void updateLoanCollateralStatus(Set<LoanCollateralManagement> loanCollateralManagementSet, boolean isReleased);
|
||||
|
||||
/**
|
||||
* This method is to recalculate and accrue the income till the last accrued date. this method is used when the
|
||||
* schedule changes due to interest recalculation
|
||||
*
|
||||
* @param loan
|
||||
*/
|
||||
void recalculateAccruals(Loan loan);
|
||||
|
||||
/**
|
||||
* This method is to set a Delinquency Tag If the loan is overdue, If the loan after the repayment transaction is
|
||||
* not overdue and It has a Delinquency Tag, It is removed
|
||||
@@ -98,10 +90,7 @@ public interface LoanAccountDomainService {
|
||||
*/
|
||||
void disableStandingInstructionsLinkedToClosedLoan(Loan loan);
|
||||
|
||||
void recalculateAccruals(Loan loan, boolean isInterestCalcualtionHappened);
|
||||
|
||||
LoanTransaction creditBalanceRefund(Loan loan, LocalDate transactionDate, BigDecimal transactionAmount, String noteText,
|
||||
ExternalId externalId, PaymentDetail paymentDetail);
|
||||
|
||||
void applyFinalIncomeAccrualTransaction(Loan loan);
|
||||
}
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@
|
||||
package org.apache.fineract.portfolio.loanaccount.jobs.addperiodicaccrualentries;
|
||||
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||
@@ -39,7 +39,7 @@ public class AddPeriodicAccrualEntriesConfig {
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
@Autowired
|
||||
private LoanAccrualPlatformService loanAccrualPlatformService;
|
||||
private LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
@Bean
|
||||
protected Step addPeriodicAccrualEntriesStep() {
|
||||
@@ -55,6 +55,6 @@ public class AddPeriodicAccrualEntriesConfig {
|
||||
|
||||
@Bean
|
||||
public AddPeriodicAccrualEntriesTasklet addPeriodicAccrualEntriesTasklet() {
|
||||
return new AddPeriodicAccrualEntriesTasklet(loanAccrualPlatformService);
|
||||
return new AddPeriodicAccrualEntriesTasklet(loanAccrualsProcessingService);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
@@ -34,7 +34,7 @@ import org.springframework.batch.repeat.RepeatStatus;
|
||||
@RequiredArgsConstructor
|
||||
public class AddPeriodicAccrualEntriesTasklet implements Tasklet {
|
||||
|
||||
private final LoanAccrualPlatformService loanAccrualPlatformService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||
@@ -47,6 +47,6 @@ public class AddPeriodicAccrualEntriesTasklet implements Tasklet {
|
||||
}
|
||||
|
||||
private void addPeriodicAccruals(final LocalDate tilldate) throws MultiException {
|
||||
loanAccrualPlatformService.addPeriodicAccruals(tilldate);
|
||||
loanAccrualsProcessingService.addPeriodicAccruals(tilldate);
|
||||
}
|
||||
}
|
||||
|
||||
-33
@@ -1,33 +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.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
|
||||
public interface LoanAccrualWritePlatformService {
|
||||
|
||||
void addAccrualAccounting(Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws Exception;
|
||||
|
||||
void addPeriodicAccruals(LocalDate tilldate, Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas)
|
||||
throws Exception;
|
||||
|
||||
void addIncomeAndAccrualTransactions(Long loanId) throws Exception;
|
||||
}
|
||||
+14
-2
@@ -23,13 +23,25 @@ import java.util.Collection;
|
||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
|
||||
public interface LoanAccrualPlatformService {
|
||||
public interface LoanAccrualsProcessingService {
|
||||
|
||||
void addPeriodicAccruals(LocalDate tilldate) throws MultiException;
|
||||
|
||||
void addPeriodicAccruals(LocalDate tilldate, Loan loan) throws MultiException;
|
||||
|
||||
void addPeriodicAccruals(LocalDate tilldate, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws MultiException;
|
||||
void addAccrualAccounting(Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws Exception;
|
||||
|
||||
void addIncomeAndAccrualTransactions(Long loanId) throws Exception;
|
||||
|
||||
void reprocessExistingAccruals(Loan loan);
|
||||
|
||||
void processAccrualsForInterestRecalculation(Loan loan, boolean isInterestRecalculationEnabled);
|
||||
|
||||
void processIncomePostingAndAccruals(Loan loan);
|
||||
|
||||
void processAccrualsForLoanClosure(Loan loan);
|
||||
|
||||
void processAccrualsForLoanForeClosure(Loan loan, LocalDate foreClosureDate, Collection<LoanTransaction> newAccrualTransactions);
|
||||
}
|
||||
Reference in New Issue
Block a user