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);
|
||||
}
|
||||
+3
-3
@@ -33,12 +33,12 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class AccrualAccountingWritePlatformServiceImpl implements AccrualAccountingWritePlatformService {
|
||||
|
||||
private final LoanAccrualPlatformService loanAccrualPlatformService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
private final AccrualAccountingDataValidator accountingDataValidator;
|
||||
|
||||
@Override
|
||||
@@ -46,7 +46,7 @@ public class AccrualAccountingWritePlatformServiceImpl implements AccrualAccount
|
||||
this.accountingDataValidator.validateLoanPeriodicAccrualData(command.json());
|
||||
LocalDate tillDate = command.localDateValueOfParameterNamed(accrueTillParamName);
|
||||
try {
|
||||
this.loanAccrualPlatformService.addPeriodicAccruals(tillDate);
|
||||
this.loanAccrualsProcessingService.addPeriodicAccruals(tillDate);
|
||||
} catch (MultiException e) {
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
|
||||
+3
-3
@@ -21,7 +21,7 @@ package org.apache.fineract.accounting.accrual.starter;
|
||||
import org.apache.fineract.accounting.accrual.serialization.AccrualAccountingDataValidator;
|
||||
import org.apache.fineract.accounting.accrual.service.AccrualAccountingWritePlatformService;
|
||||
import org.apache.fineract.accounting.accrual.service.AccrualAccountingWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -32,7 +32,7 @@ public class AccountingAccrualConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccrualAccountingWritePlatformService.class)
|
||||
public AccrualAccountingWritePlatformService accrualAccountingWritePlatformService(
|
||||
LoanAccrualPlatformService loanAccrualPlatformService, AccrualAccountingDataValidator accountingDataValidator) {
|
||||
return new AccrualAccountingWritePlatformServiceImpl(loanAccrualPlatformService, accountingDataValidator);
|
||||
LoanAccrualsProcessingService loanAccrualsProcessingService, AccrualAccountingDataValidator accountingDataValidator) {
|
||||
return new AccrualAccountingWritePlatformServiceImpl(loanAccrualsProcessingService, accountingDataValidator);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ import org.apache.fineract.cob.exceptions.BusinessStepException;
|
||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@@ -32,13 +32,13 @@ import org.springframework.stereotype.Component;
|
||||
@Slf4j
|
||||
public class AddPeriodicAccrualEntriesBusinessStep implements LoanCOBBusinessStep {
|
||||
|
||||
private final LoanAccrualPlatformService loanAccrualPlatformService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
@Override
|
||||
public Loan execute(Loan loan) {
|
||||
log.debug("start processing period accrual business step for loan with Id [{}]", loan.getId());
|
||||
try {
|
||||
loanAccrualPlatformService.addPeriodicAccruals(DateUtils.getBusinessLocalDate(), loan);
|
||||
loanAccrualsProcessingService.addPeriodicAccruals(DateUtils.getBusinessLocalDate(), loan);
|
||||
} catch (MultiException e) {
|
||||
throw new BusinessStepException(String.format("Fail to process period accrual for loan id [%s]", loan.getId()), e);
|
||||
}
|
||||
|
||||
+23
-268
@@ -25,7 +25,6 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -36,14 +35,11 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
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.LoanBalanceChangedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargePaymentPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargePaymentPreBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCreditBalanceRefundPostBusinessEvent;
|
||||
@@ -71,8 +67,6 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
|
||||
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
||||
import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
|
||||
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
@@ -85,7 +79,6 @@ import org.apache.fineract.portfolio.account.domain.StandingInstructionRepositor
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
|
||||
import org.apache.fineract.portfolio.delinquency.helper.DelinquencyEffectivePauseHelper;
|
||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
|
||||
@@ -94,11 +87,10 @@ import org.apache.fineract.portfolio.delinquency.validator.LoanDelinquencyAction
|
||||
import org.apache.fineract.portfolio.group.domain.Group;
|
||||
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService;
|
||||
@@ -129,7 +121,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
private final NoteRepository noteRepository;
|
||||
private final AccountTransferRepository accountTransferRepository;
|
||||
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
|
||||
private final LoanAccrualPlatformService loanAccrualPlatformService;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanUtilService loanUtilService;
|
||||
private final StandingInstructionRepository standingInstructionRepository;
|
||||
@@ -142,6 +133,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
|
||||
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
|
||||
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -212,6 +204,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
|
||||
scheduleGeneratorDTO, isHolidayValidationDone);
|
||||
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
|
||||
|
||||
/***
|
||||
@@ -236,7 +233,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, isLoanToLoanTransfer);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
|
||||
setLoanDelinquencyTag(loan, transactionDate);
|
||||
|
||||
@@ -423,7 +421,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanChargePaymentPostBusinessEvent(newPaymentTransaction));
|
||||
return newPaymentTransaction;
|
||||
@@ -572,17 +571,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
saveLoanTransactionWithDataIntegrityViolationChecks(loanTransaction);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService
|
||||
* #recalculateAccruals(org.apache.fineract.portfolio.loanaccount.domain. Loan)
|
||||
*/
|
||||
@Override
|
||||
public void recalculateAccruals(Loan loan) {
|
||||
recalculateAccruals(loan, loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoanDelinquencyTag(final Loan loan, final LocalDate transactionDate) {
|
||||
LoanScheduleDelinquencyData loanDelinquencyData = new LoanScheduleDelinquencyData(loan.getId(), transactionDate, null, loan);
|
||||
@@ -615,98 +603,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recalculateAccruals(Loan loan, boolean isInterestCalculationHappened) {
|
||||
LocalDate accruedTill = loan.getAccruedTill();
|
||||
if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !isInterestCalculationHappened || accruedTill == null || loan.isNpa()
|
||||
|| !loan.getStatus().isActive() || loan.isChargedOff()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isOrganisationDateEnabled = this.configurationDomainService.isOrganisationstartDateEnabled();
|
||||
LocalDate organisationStartDate = DateUtils.getBusinessLocalDate();
|
||||
if (isOrganisationDateEnabled) {
|
||||
organisationStartDate = this.configurationDomainService.retrieveOrganisationStartDate();
|
||||
}
|
||||
Collection<LoanScheduleAccrualData> loanScheduleAccrualList = new ArrayList<>();
|
||||
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||
Long loanId = loan.getId();
|
||||
Long officeId = loan.getOfficeId();
|
||||
LocalDate accrualStartDate = null;
|
||||
PeriodFrequencyType repaymentFrequency = loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType();
|
||||
Integer repayEvery = loan.repaymentScheduleDetail().getRepayEvery();
|
||||
LocalDate interestCalculatedFrom = loan.getInterestChargedFromDate();
|
||||
Long loanProductId = loan.productId();
|
||||
MonetaryCurrency currency = loan.getCurrency();
|
||||
ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
CurrencyData currencyData = applicationCurrency.toData();
|
||||
Set<LoanCharge> loanCharges = loan.getActiveCharges();
|
||||
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
if (DateUtils.isAfter(installment.getDueDate(), loan.getMaturityDate())) {
|
||||
accruedTill = DateUtils.getBusinessLocalDate();
|
||||
}
|
||||
if (!isOrganisationDateEnabled || DateUtils.isBefore(organisationStartDate, installment.getDueDate())) {
|
||||
boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
|
||||
generateLoanScheduleAccrualData(accruedTill, loanScheduleAccrualList, loanId, officeId, accrualStartDate,
|
||||
repaymentFrequency, repayEvery, interestCalculatedFrom, loanProductId, currency, currencyData, loanCharges,
|
||||
installment, isFirstNormalInstallment);
|
||||
}
|
||||
}
|
||||
|
||||
if (!loanScheduleAccrualList.isEmpty()) {
|
||||
try {
|
||||
this.loanAccrualPlatformService.addPeriodicAccruals(accruedTill, loanScheduleAccrualList);
|
||||
} catch (MultiException e) {
|
||||
String globalisationMessageCode = "error.msg.accrual.exception";
|
||||
throw new GeneralPlatformDomainRuleException(globalisationMessageCode, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void generateLoanScheduleAccrualData(final LocalDate accruedTill,
|
||||
final Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas, final Long loanId, Long officeId,
|
||||
final LocalDate accrualStartDate, final PeriodFrequencyType repaymentFrequency, final Integer repayEvery,
|
||||
final LocalDate interestCalculatedFrom, final Long loanProductId, final MonetaryCurrency currency,
|
||||
final CurrencyData currencyData, final Set<LoanCharge> loanCharges, final LoanRepaymentScheduleInstallment installment,
|
||||
boolean isFirstNormalInstallment) {
|
||||
|
||||
if (!DateUtils.isBefore(accruedTill, installment.getDueDate()) || (DateUtils.isAfter(accruedTill, installment.getFromDate())
|
||||
&& !DateUtils.isAfter(accruedTill, installment.getDueDate()))) {
|
||||
BigDecimal dueDateFeeIncome = BigDecimal.ZERO;
|
||||
BigDecimal dueDatePenaltyIncome = BigDecimal.ZERO;
|
||||
LocalDate chargesTillDate = installment.getDueDate();
|
||||
if (!DateUtils.isAfter(accruedTill, installment.getDueDate())) {
|
||||
chargesTillDate = accruedTill;
|
||||
}
|
||||
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
boolean isDue = isFirstNormalInstallment
|
||||
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(installment.getFromDate(), chargesTillDate)
|
||||
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), chargesTillDate);
|
||||
if (isDue) {
|
||||
if (loanCharge.isFeeCharge()) {
|
||||
dueDateFeeIncome = dueDateFeeIncome.add(loanCharge.amount());
|
||||
} else if (loanCharge.isPenaltyCharge()) {
|
||||
dueDatePenaltyIncome = dueDatePenaltyIncome.add(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
}
|
||||
LoanScheduleAccrualData accrualData = new LoanScheduleAccrualData(loanId, officeId, installment.getInstallmentNumber(),
|
||||
accrualStartDate, repaymentFrequency, repayEvery, installment.getDueDate(), installment.getFromDate(),
|
||||
installment.getId(), loanProductId, installment.getInterestCharged(currency).getAmount(),
|
||||
installment.getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(),
|
||||
installment.getInterestAccrued(currency).getAmount(), installment.getFeeAccrued(currency).getAmount(),
|
||||
installment.getPenaltyAccrued(currency).getAmount(), currencyData, interestCalculatedFrom,
|
||||
installment.getInterestWaived(currency).getAmount(), installment.getCreditedFee(currency).getAmount(),
|
||||
installment.getCreditedPenalty(currency).getAmount());
|
||||
loanScheduleAccrualDatas.add(accrualData);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
|
||||
final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
|
||||
if (transferTransaction != null) {
|
||||
@@ -748,7 +644,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanCreditBalanceRefundPostBusinessEvent(newCreditBalanceRefundTransaction));
|
||||
@@ -792,7 +689,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanRefundPostBusinessEvent(newRefundTransaction));
|
||||
|
||||
@@ -805,6 +703,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
@Override
|
||||
public LoanTransaction foreCloseLoan(Loan loan, final LocalDate foreClosureDate, final String noteText, final ExternalId externalId,
|
||||
Map<String, Object> changes) {
|
||||
|
||||
if (loan.isChargedOff() && DateUtils.isBefore(foreClosureDate, loan.getChargedOffOnDate())) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||
+ loan.getId()
|
||||
@@ -821,38 +720,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
|
||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = null;
|
||||
final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate);
|
||||
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()
|
||||
&& (loan.getAccruedTill() == null || !DateUtils.isEqual(foreClosureDate, loan.getAccruedTill()))) {
|
||||
loan.reverseAccrualsAfter(foreClosureDate);
|
||||
Money[] accruedReceivables = loan.getReceivableIncome(foreClosureDate);
|
||||
Money interestPortion = foreCloseDetail.getInterestCharged(currency).minus(accruedReceivables[0]);
|
||||
Money feePortion = foreCloseDetail.getFeeChargesCharged(currency).minus(accruedReceivables[1]);
|
||||
Money penaltyPortion = foreCloseDetail.getPenaltyChargesCharged(currency).minus(accruedReceivables[2]);
|
||||
Money total = interestPortion.plus(feePortion).plus(penaltyPortion);
|
||||
if (total.isGreaterThanZero()) {
|
||||
ExternalId accrualExternalId = externalIdFactory.create();
|
||||
LoanTransaction accrualTransaction = LoanTransaction.accrueTransaction(loan, loan.getOffice(), foreClosureDate,
|
||||
total.getAmount(), interestPortion.getAmount(), feePortion.getAmount(), penaltyPortion.getAmount(),
|
||||
accrualExternalId);
|
||||
LocalDate fromDate = loan.getDisbursementDate();
|
||||
if (loan.getAccruedTill() != null) {
|
||||
fromDate = loan.getAccruedTill();
|
||||
}
|
||||
newTransactions.add(accrualTransaction);
|
||||
loan.addLoanTransaction(accrualTransaction);
|
||||
Set<LoanChargePaidBy> accrualCharges = accrualTransaction.getLoanChargesPaid();
|
||||
for (LoanCharge loanCharge : loan.getActiveCharges()) {
|
||||
boolean isDue = DateUtils.isEqual(fromDate, loan.getDisbursementDate())
|
||||
? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(fromDate, foreClosureDate)
|
||||
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, foreClosureDate);
|
||||
if (loanCharge.isActive() && !loanCharge.isPaid() && (isDue || loanCharge.isInstalmentFee())) {
|
||||
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrualTransaction, loanCharge,
|
||||
loanCharge.getAmountOutstanding(currency).getAmount(), null);
|
||||
accrualCharges.add(loanChargePaidBy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loanAccrualsProcessingService.processAccrualsForLoanForeClosure(loan, foreClosureDate, newTransactions);
|
||||
|
||||
Money interestPayable = foreCloseDetail.getInterestCharged(currency);
|
||||
Money feePayable = foreCloseDetail.getFeeChargesCharged(currency);
|
||||
@@ -874,6 +743,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.handleForeClosureTransactions(payment,
|
||||
defaultLoanLifecycleStateMachine, scheduleGeneratorDTO);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
/***
|
||||
* TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the
|
||||
* time being, not a major issue for now as this loop is entered only in edge cases (when a payment is made
|
||||
@@ -927,123 +801,4 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFinalIncomeAccrualTransaction(Loan loan) {
|
||||
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()
|
||||
// to avoid collision with processIncomeAccrualTransactionOnLoanClosure()
|
||||
&& !(loan.getLoanInterestRecalculationDetails() != null
|
||||
&& loan.getLoanInterestRecalculationDetails().isCompoundingToBePostedAsTransaction())
|
||||
&& !loan.isNpa() && !loan.isChargedOff()) {
|
||||
|
||||
MonetaryCurrency currency = loan.getCurrency();
|
||||
Money interestPortion = Money.zero(currency);
|
||||
Money feePortion = Money.zero(currency);
|
||||
Money penaltyPortion = Money.zero(currency);
|
||||
|
||||
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : loan.getRepaymentScheduleInstallments()) {
|
||||
// TODO: test with interest waiving
|
||||
interestPortion = interestPortion.add(loanRepaymentScheduleInstallment.getInterestCharged(currency))
|
||||
.minus(loanRepaymentScheduleInstallment.getInterestAccrued(currency))
|
||||
.minus(loanRepaymentScheduleInstallment.getInterestWaived(currency));
|
||||
}
|
||||
|
||||
for (LoanCharge loanCharge : loan.getLoanCharges()) {
|
||||
if (!loanCharge.isActive()) {
|
||||
continue;
|
||||
}
|
||||
BigDecimal accruedAmount = BigDecimal.ZERO;
|
||||
BigDecimal waivedAmount = BigDecimal.ZERO;
|
||||
for (LoanChargePaidBy loanChargePaidBy : loanCharge.getLoanChargePaidBySet()) {
|
||||
if (loanChargePaidBy.getLoanTransaction().isAccrual()) {
|
||||
accruedAmount = accruedAmount.add(loanChargePaidBy.getLoanTransaction().getAmount());
|
||||
} else if (loanChargePaidBy.getLoanTransaction().isChargesWaiver()) {
|
||||
waivedAmount = waivedAmount.add(loanChargePaidBy.getLoanTransaction().getAmount());
|
||||
}
|
||||
}
|
||||
Money needToAccrueAmount = MathUtil.negativeToZero(loanCharge.getAmount(currency).minus(accruedAmount).minus(waivedAmount));
|
||||
if (loanCharge.isPenaltyCharge()) {
|
||||
penaltyPortion = penaltyPortion.add(needToAccrueAmount);
|
||||
} else if (loanCharge.isFeeCharge()) {
|
||||
feePortion = feePortion.add(needToAccrueAmount);
|
||||
}
|
||||
}
|
||||
|
||||
Money total = interestPortion.plus(feePortion).plus(penaltyPortion);
|
||||
|
||||
if (total.isGreaterThanZero()) {
|
||||
ExternalId externalId = externalIdFactory.create();
|
||||
|
||||
LocalDate accrualTransactionDate = getFinalAccrualTransactionDate(loan);
|
||||
|
||||
LoanTransaction accrualTransaction = LoanTransaction.accrueTransaction(loan, loan.getOffice(), accrualTransactionDate,
|
||||
total.getAmount(), interestPortion.getAmount(), feePortion.getAmount(), penaltyPortion.getAmount(), externalId);
|
||||
|
||||
Set<LoanChargePaidBy> accrualCharges = accrualTransaction.getLoanChargesPaid();
|
||||
|
||||
Map<Long, Money> accrualDetails = loan.getActiveCharges().stream()
|
||||
.collect(Collectors.toMap(LoanCharge::getId, v -> Money.zero(currency)));
|
||||
|
||||
loan.getLoanTransactions(LoanTransaction::isAccrual).forEach(transaction -> {
|
||||
transaction.getLoanChargesPaid().forEach(loanChargePaid -> {
|
||||
accrualDetails.computeIfPresent(loanChargePaid.getLoanCharge().getId(),
|
||||
(mappedKey, mappedValue) -> mappedValue.add(Money.of(currency, loanChargePaid.getAmount())));
|
||||
});
|
||||
});
|
||||
|
||||
loan.getActiveCharges().forEach(loanCharge -> {
|
||||
Money amount = loanCharge.getAmount(currency).minus(loanCharge.getAmountWaived(currency));
|
||||
if (!loanCharge.isInstalmentFee() && loanCharge.isActive()
|
||||
&& accrualDetails.get(loanCharge.getId()).isLessThan(amount)) {
|
||||
Money amountToBeAccrued = amount.minus(accrualDetails.get(loanCharge.getId()));
|
||||
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrualTransaction, loanCharge,
|
||||
amountToBeAccrued.getAmount(), null);
|
||||
accrualCharges.add(loanChargePaidBy);
|
||||
}
|
||||
});
|
||||
|
||||
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : loan.getRepaymentScheduleInstallments()) {
|
||||
for (LoanInstallmentCharge installmentCharge : loanRepaymentScheduleInstallment.getInstallmentCharges()) {
|
||||
if (installmentCharge.getLoanCharge().isActive()) {
|
||||
Money notWaivedAmount = installmentCharge.getAmount(currency)
|
||||
.minus(installmentCharge.getAmountWaived(currency));
|
||||
if (notWaivedAmount.isGreaterThanZero()) {
|
||||
Money amountToBeAccrued = notWaivedAmount
|
||||
.minus(accrualDetails.get(installmentCharge.getLoanCharge().getId()));
|
||||
if (amountToBeAccrued.isGreaterThanZero()) {
|
||||
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrualTransaction,
|
||||
installmentCharge.getLoanCharge(), amountToBeAccrued.getAmount(),
|
||||
installmentCharge.getInstallment().getInstallmentNumber());
|
||||
accrualCharges.add(loanChargePaidBy);
|
||||
accrualDetails.computeIfPresent(installmentCharge.getLoanCharge().getId(),
|
||||
(mappedKey, mappedValue) -> mappedValue.add(amountToBeAccrued));
|
||||
}
|
||||
accrualDetails.computeIfPresent(installmentCharge.getLoanCharge().getId(),
|
||||
(mappedKey, mappedValue) -> MathUtil
|
||||
.negativeToZero(mappedValue.minus(Money.of(currency, installmentCharge.getAmount()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
saveLoanTransactionWithDataIntegrityViolationChecks(accrualTransaction);
|
||||
loan.addLoanTransaction(accrualTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction));
|
||||
|
||||
loan.getRepaymentScheduleInstallments().forEach(installment -> {
|
||||
installment.updateAccrualPortion(
|
||||
installment.getInterestCharged(currency).minus(installment.getInterestWaived(currency)),
|
||||
installment.getFeeChargesCharged(currency).minus(installment.getFeeChargesWaived(currency)),
|
||||
installment.getPenaltyChargesCharged(currency).minus(installment.getPenaltyChargesWaived(currency)));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate getFinalAccrualTransactionDate(Loan loan) {
|
||||
return switch (loan.getStatus()) {
|
||||
case CLOSED_OBLIGATIONS_MET -> loan.getClosedOnDate();
|
||||
case OVERPAID -> loan.getOverpaidOnDate();
|
||||
default -> throw new IllegalStateException("Unexpected value: " + loan.getStatus());
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -27,7 +27,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
@@ -41,7 +41,7 @@ import org.springframework.stereotype.Component;
|
||||
public class AddAccrualEntriesTasklet implements Tasklet {
|
||||
|
||||
private final LoanReadPlatformService loanReadPlatformService;
|
||||
private final LoanAccrualWritePlatformService loanAccrualWritePlatformService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||
@@ -60,7 +60,7 @@ public class AddAccrualEntriesTasklet implements Tasklet {
|
||||
List<Throwable> errors = new ArrayList<>();
|
||||
for (Map.Entry<Long, Collection<LoanScheduleAccrualData>> mapEntry : loanDataMap.entrySet()) {
|
||||
try {
|
||||
loanAccrualWritePlatformService.addAccrualAccounting(mapEntry.getKey(), mapEntry.getValue());
|
||||
loanAccrualsProcessingService.addAccrualAccounting(mapEntry.getKey(), mapEntry.getValue());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to add accrual transaction for loan {}", mapEntry.getKey(), e);
|
||||
errors.add(e);
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@
|
||||
package org.apache.fineract.portfolio.loanaccount.jobs.addperiodicaccrualentriesforloanswithincomepostedastransactions;
|
||||
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
@@ -42,7 +42,7 @@ public class AddPeriodicAccrualEntriesForLoansConfig {
|
||||
@Autowired
|
||||
private LoanReadPlatformService loanReadPlatformService;
|
||||
@Autowired
|
||||
private LoanAccrualWritePlatformService loanAccrualWritePlatformService;
|
||||
private LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
@Bean
|
||||
protected Step addPeriodicAccrualEntriesForLoansWithIncomePostedAsTransactionsStep() {
|
||||
@@ -58,6 +58,6 @@ public class AddPeriodicAccrualEntriesForLoansConfig {
|
||||
|
||||
@Bean
|
||||
public AddPeriodicAccrualEntriesForLoansTasklet addPeriodicAccrualEntriesForLoansWithIncomePostedAsTransactionsTasklet() {
|
||||
return new AddPeriodicAccrualEntriesForLoansTasklet(loanReadPlatformService, loanAccrualWritePlatformService);
|
||||
return new AddPeriodicAccrualEntriesForLoansTasklet(loanReadPlatformService, loanAccrualsProcessingService);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
@@ -37,7 +37,7 @@ import org.springframework.batch.repeat.RepeatStatus;
|
||||
public class AddPeriodicAccrualEntriesForLoansTasklet implements Tasklet {
|
||||
|
||||
private final LoanReadPlatformService loanReadPlatformService;
|
||||
private final LoanAccrualWritePlatformService loanAccrualWritePlatformService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||
@@ -46,7 +46,7 @@ public class AddPeriodicAccrualEntriesForLoansTasklet implements Tasklet {
|
||||
List<Throwable> errors = new ArrayList<>();
|
||||
for (Long loanId : loanIds) {
|
||||
try {
|
||||
loanAccrualWritePlatformService.addIncomeAndAccrualTransactions(loanId);
|
||||
loanAccrualsProcessingService.addIncomeAndAccrualTransactions(loanId);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to add income and accrual transaction for loan {}", loanId, e);
|
||||
errors.add(e);
|
||||
|
||||
+5
@@ -113,6 +113,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.VariableLoanScheduleFromApiJsonValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanDisbursementDetailsAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||
@@ -156,6 +157,7 @@ public class LoanScheduleAssembler {
|
||||
private final LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler;
|
||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
|
||||
final Long loanProductId = this.fromApiJsonHelper.extractLongNamed("productId", element);
|
||||
@@ -915,6 +917,8 @@ public class LoanScheduleAssembler {
|
||||
final LocalDate recalculateFrom = null;
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO);
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
|
||||
}
|
||||
|
||||
private List<LoanTermVariations> adjustExistingVariations(List<LoanTermVariations> variations, List<LoanTermVariations> newVariations,
|
||||
@@ -1462,6 +1466,7 @@ public class LoanScheduleAssembler {
|
||||
if (actualChanges.containsKey(LoanApiConstants.approvedLoanAmountParameterName)
|
||||
|| actualChanges.containsKey("recalculateLoanSchedule") || actualChanges.containsKey("expectedDisbursementDate")) {
|
||||
loan.regenerateRepaymentSchedule(loanUtilService.buildScheduleGeneratorDTO(loan, null));
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -35,6 +35,7 @@ import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -50,6 +51,7 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl
|
||||
private final LoanScheduleAssembler loanScheduleAssembler;
|
||||
private final LoanUtilService loanUtilService;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult addLoanScheduleVariations(final Long loanId, final JsonCommand command) {
|
||||
@@ -97,6 +99,7 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl
|
||||
final LocalDate recalculateFrom = null;
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO);
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsDeletedBusinessEvent(loan));
|
||||
return new CommandProcessingResultBuilder() //
|
||||
|
||||
+4
-1
@@ -80,6 +80,7 @@ import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanResch
|
||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService;
|
||||
@@ -119,6 +120,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
||||
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
|
||||
private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
/**
|
||||
* create a new instance of the LoanRescheduleRequest object from the JsonCommand object and persist
|
||||
@@ -447,6 +449,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
||||
} else {
|
||||
loan.updateLoanSchedule(loanSchedule.getLoanScheduleModel());
|
||||
}
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
loan.recalculateAllCharges();
|
||||
ChangedTransactionDetail changedTransactionDetail = loan.processTransactions();
|
||||
|
||||
@@ -476,7 +479,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
this.loanAccountDomainService.recalculateAccruals(loan, true);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, true);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueAdjustScheduleBusinessEvent(loan));
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId)
|
||||
|
||||
-81
@@ -1,81 +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.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformService {
|
||||
|
||||
private final LoanReadPlatformService loanReadPlatformService;
|
||||
private final LoanAccrualWritePlatformService loanAccrualWritePlatformService;
|
||||
|
||||
@Override
|
||||
public void addPeriodicAccruals(final LocalDate tillDate) throws JobExecutionException {
|
||||
Collection<LoanScheduleAccrualData> loanScheduleAccrualDataList = this.loanReadPlatformService
|
||||
.retrievePeriodicAccrualData(tillDate);
|
||||
addPeriodicAccruals(tillDate, loanScheduleAccrualDataList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPeriodicAccruals(final LocalDate tillDate, Loan loan) throws JobExecutionException {
|
||||
Collection<LoanScheduleAccrualData> loanScheduleAccrualDataList = this.loanReadPlatformService.retrievePeriodicAccrualData(tillDate,
|
||||
loan);
|
||||
addPeriodicAccruals(tillDate, loanScheduleAccrualDataList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPeriodicAccruals(final LocalDate tillDate, Collection<LoanScheduleAccrualData> loanScheduleAccrualDataList)
|
||||
throws JobExecutionException {
|
||||
Map<Long, Collection<LoanScheduleAccrualData>> loanDataMap = new HashMap<>();
|
||||
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualDataList) {
|
||||
if (loanDataMap.containsKey(accrualData.getLoanId())) {
|
||||
loanDataMap.get(accrualData.getLoanId()).add(accrualData);
|
||||
} else {
|
||||
Collection<LoanScheduleAccrualData> accrualDataList = new ArrayList<>();
|
||||
accrualDataList.add(accrualData);
|
||||
loanDataMap.put(accrualData.getLoanId(), accrualDataList);
|
||||
}
|
||||
}
|
||||
|
||||
List<Throwable> errors = new ArrayList<>();
|
||||
for (Map.Entry<Long, Collection<LoanScheduleAccrualData>> mapEntry : loanDataMap.entrySet()) {
|
||||
try {
|
||||
this.loanAccrualWritePlatformService.addPeriodicAccruals(tillDate, mapEntry.getKey(), mapEntry.getValue());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to add accrual transaction for loan {}", mapEntry.getKey(), e);
|
||||
errors.add(e);
|
||||
}
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
throw new JobExecutionException(errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
-589
@@ -1,589 +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 static org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction.accrueTransaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
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.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlatformService {
|
||||
|
||||
private static final String ACCRUAL_ON_CHARGE_DUE_DATE = "due-date";
|
||||
private static final String ACCRUAL_ON_CHARGE_SUBMITTED_ON_DATE = "submitted-date";
|
||||
private final LoanReadPlatformService loanReadPlatformService;
|
||||
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
|
||||
private final PlatformSecurityContext context;
|
||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||
private final LoanRepository loanRepository;
|
||||
private final OfficeRepository officeRepository;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void addAccrualAccounting(final Long loanId, final Collection<LoanScheduleAccrualData> loanScheduleAccrualData) {
|
||||
Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService.retrieveLoanChargesForAccrual(loanId);
|
||||
Collection<LoanSchedulePeriodData> loanWaiverScheduleData = new ArrayList<>(1);
|
||||
Collection<LoanTransactionData> loanWaiverTransactionData = new ArrayList<>(1);
|
||||
|
||||
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualData) {
|
||||
if (accrualData.getWaivedInterestIncome() != null && loanWaiverScheduleData.isEmpty()) {
|
||||
loanWaiverScheduleData = this.loanReadPlatformService.fetchWaiverInterestRepaymentData(accrualData.getLoanId());
|
||||
loanWaiverTransactionData = this.loanReadPlatformService.retrieveWaiverLoanTransactions(accrualData.getLoanId());
|
||||
}
|
||||
updateCharges(chargeData, accrualData, accrualData.getFromDateAsLocaldate(), accrualData.getDueDateAsLocaldate());
|
||||
updateInterestIncome(accrualData, loanWaiverTransactionData, loanWaiverScheduleData, accrualData.getDueDateAsLocaldate());
|
||||
addAccrualAccounting(accrualData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void addPeriodicAccruals(final LocalDate tillDate, Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualData) {
|
||||
boolean firstTime = true;
|
||||
LocalDate accruedTill = null;
|
||||
Collection<LoanChargeData> chargeData = this.loanChargeReadPlatformService.retrieveLoanChargesForAccrual(loanId);
|
||||
Collection<LoanSchedulePeriodData> loanWaiverScheduleData = new ArrayList<>(1);
|
||||
Collection<LoanTransactionData> loanWaiverTransactionData = new ArrayList<>(1);
|
||||
for (final LoanScheduleAccrualData accrualData : loanScheduleAccrualData) {
|
||||
if (accrualData.getWaivedInterestIncome() != null && loanWaiverScheduleData.isEmpty()) {
|
||||
loanWaiverScheduleData = this.loanReadPlatformService.fetchWaiverInterestRepaymentData(accrualData.getLoanId());
|
||||
loanWaiverTransactionData = this.loanReadPlatformService.retrieveWaiverLoanTransactions(accrualData.getLoanId());
|
||||
}
|
||||
|
||||
if (DateUtils.isAfter(accrualData.getDueDateAsLocaldate(), tillDate)) {
|
||||
if (accruedTill == null || firstTime) {
|
||||
accruedTill = accrualData.getAccruedTill();
|
||||
firstTime = false;
|
||||
}
|
||||
if (accruedTill == null || DateUtils.isBefore(accruedTill, tillDate)) {
|
||||
updateCharges(chargeData, accrualData, accrualData.getFromDateAsLocaldate(), tillDate);
|
||||
updateInterestIncome(accrualData, loanWaiverTransactionData, loanWaiverScheduleData, tillDate);
|
||||
addAccrualTillSpecificDate(tillDate, accrualData);
|
||||
}
|
||||
} else {
|
||||
updateCharges(chargeData, accrualData, accrualData.getFromDateAsLocaldate(), accrualData.getDueDateAsLocaldate());
|
||||
updateInterestIncome(accrualData, loanWaiverTransactionData, loanWaiverScheduleData, tillDate);
|
||||
addAccrualAccounting(accrualData);
|
||||
accruedTill = accrualData.getDueDateAsLocaldate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addAccrualTillSpecificDate(final LocalDate tillDate, final LoanScheduleAccrualData accrualData) {
|
||||
LocalDate interestStartDate = accrualData.getFromDateAsLocaldate();
|
||||
if (DateUtils.isBefore(accrualData.getFromDateAsLocaldate(), accrualData.getInterestCalculatedFrom())) {
|
||||
if (DateUtils.isBefore(accrualData.getInterestCalculatedFrom(), accrualData.getDueDateAsLocaldate())) {
|
||||
interestStartDate = accrualData.getInterestCalculatedFrom();
|
||||
} else {
|
||||
interestStartDate = accrualData.getDueDateAsLocaldate();
|
||||
}
|
||||
}
|
||||
|
||||
int totalNumberOfDays = Math.toIntExact(ChronoUnit.DAYS.between(interestStartDate, accrualData.getDueDateAsLocaldate()));
|
||||
LocalDate startDate = accrualData.getFromDateAsLocaldate();
|
||||
if (DateUtils.isBefore(startDate, accrualData.getInterestCalculatedFrom())) {
|
||||
if (DateUtils.isBefore(accrualData.getInterestCalculatedFrom(), tillDate)) {
|
||||
startDate = accrualData.getInterestCalculatedFrom();
|
||||
} else {
|
||||
startDate = tillDate;
|
||||
}
|
||||
}
|
||||
int daysToBeAccrued = Math.toIntExact(ChronoUnit.DAYS.between(startDate, tillDate));
|
||||
double interestPerDay = accrualData.getAccruableIncome().doubleValue() / totalNumberOfDays;
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
BigDecimal interestPortion;
|
||||
BigDecimal feePortion = accrualData.getDueDateFeeIncome();
|
||||
BigDecimal penaltyPortion = accrualData.getDueDatePenaltyIncome();
|
||||
if (daysToBeAccrued >= totalNumberOfDays) {
|
||||
interestPortion = accrualData.getAccruableIncome();
|
||||
} else {
|
||||
interestPortion = BigDecimal.valueOf(interestPerDay * daysToBeAccrued);
|
||||
}
|
||||
interestPortion = interestPortion.setScale(accrualData.getCurrencyData().getDecimalPlaces(), MoneyHelper.getRoundingMode());
|
||||
|
||||
BigDecimal totalAccInterest = accrualData.getAccruedInterestIncome();
|
||||
BigDecimal totalAccPenalty = accrualData.getAccruedPenaltyIncome();
|
||||
BigDecimal totalCreditedPenalty = accrualData.getCreditedPenalty();
|
||||
BigDecimal totalAccFee = accrualData.getAccruedFeeIncome();
|
||||
BigDecimal totalCreditedFee = accrualData.getCreditedFee();
|
||||
|
||||
if (totalAccInterest == null) {
|
||||
totalAccInterest = BigDecimal.ZERO;
|
||||
}
|
||||
interestPortion = interestPortion.subtract(totalAccInterest);
|
||||
amount = amount.add(interestPortion);
|
||||
totalAccInterest = totalAccInterest.add(interestPortion);
|
||||
if (interestPortion.compareTo(BigDecimal.ZERO) == 0) {
|
||||
interestPortion = null;
|
||||
}
|
||||
if (feePortion != null) {
|
||||
if (totalAccFee == null) {
|
||||
totalAccFee = BigDecimal.ZERO;
|
||||
}
|
||||
if (totalCreditedFee == null) {
|
||||
totalCreditedFee = BigDecimal.ZERO;
|
||||
}
|
||||
feePortion = feePortion.subtract(totalAccFee).subtract(totalCreditedFee);
|
||||
amount = amount.add(feePortion);
|
||||
totalAccFee = totalAccFee.add(feePortion);
|
||||
if (feePortion.compareTo(BigDecimal.ZERO) == 0) {
|
||||
feePortion = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (penaltyPortion != null) {
|
||||
if (totalAccPenalty == null) {
|
||||
totalAccPenalty = BigDecimal.ZERO;
|
||||
}
|
||||
if (totalCreditedPenalty == null) {
|
||||
totalCreditedPenalty = BigDecimal.ZERO;
|
||||
}
|
||||
penaltyPortion = penaltyPortion.subtract(totalAccPenalty).subtract(totalCreditedPenalty);
|
||||
amount = amount.add(penaltyPortion);
|
||||
totalAccPenalty = totalAccPenalty.add(penaltyPortion);
|
||||
if (penaltyPortion.compareTo(BigDecimal.ZERO) == 0) {
|
||||
penaltyPortion = null;
|
||||
}
|
||||
}
|
||||
if (amount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
addAccrualAccounting(accrualData, amount, interestPortion, totalAccInterest, feePortion, totalAccFee, penaltyPortion,
|
||||
totalAccPenalty, tillDate);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData) {
|
||||
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
BigDecimal interestPortion = null;
|
||||
BigDecimal totalAccInterest = null;
|
||||
if (scheduleAccrualData.getAccruableIncome() != null) {
|
||||
interestPortion = scheduleAccrualData.getAccruableIncome();
|
||||
totalAccInterest = interestPortion;
|
||||
if (scheduleAccrualData.getAccruedInterestIncome() != null) {
|
||||
interestPortion = interestPortion.subtract(scheduleAccrualData.getAccruedInterestIncome());
|
||||
}
|
||||
amount = amount.add(interestPortion);
|
||||
if (interestPortion.compareTo(BigDecimal.ZERO) == 0) {
|
||||
interestPortion = null;
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal feePortion = null;
|
||||
BigDecimal totalAccFee = null;
|
||||
if (scheduleAccrualData.getDueDateFeeIncome() != null) {
|
||||
feePortion = scheduleAccrualData.getDueDateFeeIncome();
|
||||
totalAccFee = feePortion;
|
||||
if (scheduleAccrualData.getAccruedFeeIncome() != null) {
|
||||
feePortion = feePortion.subtract(scheduleAccrualData.getAccruedFeeIncome());
|
||||
}
|
||||
if (scheduleAccrualData.getCreditedFee() != null) {
|
||||
feePortion = feePortion.subtract(scheduleAccrualData.getCreditedFee());
|
||||
}
|
||||
amount = amount.add(feePortion);
|
||||
if (feePortion.compareTo(BigDecimal.ZERO) == 0) {
|
||||
feePortion = null;
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal penaltyPortion = null;
|
||||
BigDecimal totalAccPenalty = null;
|
||||
if (scheduleAccrualData.getDueDatePenaltyIncome() != null) {
|
||||
penaltyPortion = scheduleAccrualData.getDueDatePenaltyIncome();
|
||||
totalAccPenalty = penaltyPortion;
|
||||
if (scheduleAccrualData.getAccruedPenaltyIncome() != null) {
|
||||
penaltyPortion = penaltyPortion.subtract(scheduleAccrualData.getAccruedPenaltyIncome());
|
||||
}
|
||||
if (scheduleAccrualData.getCreditedPenalty() != null) {
|
||||
penaltyPortion = penaltyPortion.subtract(scheduleAccrualData.getCreditedPenalty());
|
||||
}
|
||||
amount = amount.add(penaltyPortion);
|
||||
if (penaltyPortion.compareTo(BigDecimal.ZERO) == 0) {
|
||||
penaltyPortion = null;
|
||||
}
|
||||
}
|
||||
if (amount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
final String chargeAccrualDateCriteria = configurationDomainService.getAccrualDateConfigForCharge();
|
||||
if (chargeAccrualDateCriteria.equalsIgnoreCase(ACCRUAL_ON_CHARGE_DUE_DATE)) {
|
||||
addAccrualAccounting(scheduleAccrualData, amount, interestPortion, totalAccInterest, feePortion, totalAccFee,
|
||||
penaltyPortion, totalAccPenalty, scheduleAccrualData.getDueDateAsLocaldate());
|
||||
} else if (chargeAccrualDateCriteria.equalsIgnoreCase(ACCRUAL_ON_CHARGE_SUBMITTED_ON_DATE)) {
|
||||
addAccrualAccounting(scheduleAccrualData, amount, interestPortion, totalAccInterest, feePortion, totalAccFee,
|
||||
penaltyPortion, totalAccPenalty, DateUtils.getBusinessLocalDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData, BigDecimal amount, BigDecimal interestPortion,
|
||||
BigDecimal totalAccInterest, BigDecimal feePortion, BigDecimal totalAccFee, BigDecimal penaltyPortion,
|
||||
BigDecimal totalAccPenalty, final LocalDate accruedTill) throws DataAccessException {
|
||||
AppUser user = context.authenticatedUser();
|
||||
Loan loan = loanRepository.getReferenceById(scheduleAccrualData.getLoanId());
|
||||
Office office = officeRepository.getReferenceById(scheduleAccrualData.getOfficeId());
|
||||
LoanTransaction loanTransaction = loanTransactionRepository.saveAndFlush(accrueTransaction(loan, office, accruedTill, amount,
|
||||
interestPortion, feePortion, penaltyPortion, externalIdFactory.create()));
|
||||
|
||||
Map<LoanChargeData, BigDecimal> applicableCharges = scheduleAccrualData.getApplicableCharges();
|
||||
String chargesPaidSql = "INSERT INTO m_loan_charge_paid_by (loan_transaction_id, loan_charge_id, amount,installment_number) VALUES (?,?,?,?)";
|
||||
for (Map.Entry<LoanChargeData, BigDecimal> entry : applicableCharges.entrySet()) {
|
||||
LoanChargeData chargeData = entry.getKey();
|
||||
this.jdbcTemplate.update(chargesPaidSql, loanTransaction.getId(), chargeData.getId(), entry.getValue(),
|
||||
scheduleAccrualData.getInstallmentNumber());
|
||||
}
|
||||
|
||||
Map<String, Object> transactionMap = toMapData(loanTransaction.getId(), amount, interestPortion, feePortion, penaltyPortion,
|
||||
scheduleAccrualData, accruedTill);
|
||||
|
||||
String repaymentUpdateSql = "UPDATE m_loan_repayment_schedule SET accrual_interest_derived=?, accrual_fee_charges_derived=?, "
|
||||
+ "accrual_penalty_charges_derived=? WHERE id=?";
|
||||
this.jdbcTemplate.update(repaymentUpdateSql, totalAccInterest, totalAccFee, totalAccPenalty,
|
||||
scheduleAccrualData.getRepaymentScheduleId());
|
||||
|
||||
String updateLoan = "UPDATE m_loan SET accrued_till=?, last_modified_by=?, last_modified_on_utc=? WHERE id=?";
|
||||
this.jdbcTemplate.update(updateLoan, accruedTill, user.getId(), DateUtils.getAuditOffsetDateTime(),
|
||||
scheduleAccrualData.getLoanId());
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(loanTransaction));
|
||||
|
||||
final Map<String, Object> accountingBridgeData = deriveAccountingBridgeData(scheduleAccrualData, transactionMap);
|
||||
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
||||
}
|
||||
|
||||
private Map<String, Object> deriveAccountingBridgeData(final LoanScheduleAccrualData loanScheduleAccrualData,
|
||||
final Map<String, Object> transactionMap) {
|
||||
|
||||
final Map<String, Object> accountingBridgeData = new LinkedHashMap<>();
|
||||
accountingBridgeData.put("loanId", loanScheduleAccrualData.getLoanId());
|
||||
accountingBridgeData.put("loanProductId", loanScheduleAccrualData.getLoanProductId());
|
||||
accountingBridgeData.put("officeId", loanScheduleAccrualData.getOfficeId());
|
||||
accountingBridgeData.put("currencyCode", loanScheduleAccrualData.getCurrencyData().getCode());
|
||||
accountingBridgeData.put("cashBasedAccountingEnabled", false);
|
||||
accountingBridgeData.put("upfrontAccrualBasedAccountingEnabled", false);
|
||||
accountingBridgeData.put("periodicAccrualBasedAccountingEnabled", true);
|
||||
accountingBridgeData.put("isAccountTransfer", false);
|
||||
accountingBridgeData.put("isChargeOff", false);
|
||||
accountingBridgeData.put("isFraud", false);
|
||||
|
||||
final List<Map<String, Object>> newLoanTransactions = new ArrayList<>();
|
||||
newLoanTransactions.add(transactionMap);
|
||||
|
||||
accountingBridgeData.put("newLoanTransactions", newLoanTransactions);
|
||||
return accountingBridgeData;
|
||||
}
|
||||
|
||||
public Map<String, Object> toMapData(final Long id, final BigDecimal amount, final BigDecimal interestPortion,
|
||||
final BigDecimal feePortion, final BigDecimal penaltyPortion, final LoanScheduleAccrualData loanScheduleAccrualData,
|
||||
final LocalDate accruedTill) {
|
||||
final Map<String, Object> thisTransactionData = new LinkedHashMap<>();
|
||||
|
||||
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.ACCRUAL);
|
||||
|
||||
thisTransactionData.put("id", id);
|
||||
thisTransactionData.put("officeId", loanScheduleAccrualData.getOfficeId());
|
||||
thisTransactionData.put("type", transactionType);
|
||||
thisTransactionData.put("reversed", false);
|
||||
thisTransactionData.put("date", accruedTill);
|
||||
thisTransactionData.put("currency", loanScheduleAccrualData.getCurrencyData());
|
||||
thisTransactionData.put("amount", amount);
|
||||
thisTransactionData.put("principalPortion", null);
|
||||
thisTransactionData.put("interestPortion", interestPortion);
|
||||
thisTransactionData.put("feeChargesPortion", feePortion);
|
||||
thisTransactionData.put("penaltyChargesPortion", penaltyPortion);
|
||||
thisTransactionData.put("overPaymentPortion", null);
|
||||
|
||||
Map<LoanChargeData, BigDecimal> applicableCharges = loanScheduleAccrualData.getApplicableCharges();
|
||||
if (applicableCharges != null && !applicableCharges.isEmpty()) {
|
||||
final List<Map<String, Object>> loanChargesPaidData = new ArrayList<>();
|
||||
for (Map.Entry<LoanChargeData, BigDecimal> entry : applicableCharges.entrySet()) {
|
||||
LoanChargeData chargeData = entry.getKey();
|
||||
final Map<String, Object> loanChargePaidData = new LinkedHashMap<>();
|
||||
loanChargePaidData.put("chargeId", chargeData.getChargeId());
|
||||
loanChargePaidData.put("isPenalty", chargeData.isPenalty());
|
||||
loanChargePaidData.put("loanChargeId", chargeData.getId());
|
||||
loanChargePaidData.put("amount", entry.getValue());
|
||||
|
||||
loanChargesPaidData.add(loanChargePaidData);
|
||||
}
|
||||
thisTransactionData.put("loanChargesPaid", loanChargesPaidData);
|
||||
}
|
||||
|
||||
return thisTransactionData;
|
||||
}
|
||||
|
||||
private void updateCharges(final Collection<LoanChargeData> chargesData, final LoanScheduleAccrualData accrualData,
|
||||
final LocalDate startDate, final LocalDate endDate) {
|
||||
final String chargeAccrualDateCriteria = configurationDomainService.getAccrualDateConfigForCharge();
|
||||
if (chargeAccrualDateCriteria.equalsIgnoreCase(ACCRUAL_ON_CHARGE_DUE_DATE)) {
|
||||
updateChargeForDueDate(chargesData, accrualData, startDate, endDate);
|
||||
} else if (chargeAccrualDateCriteria.equalsIgnoreCase(ACCRUAL_ON_CHARGE_SUBMITTED_ON_DATE)) {
|
||||
updateChargeForSubmittedOnDate(chargesData, accrualData, startDate, endDate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void updateChargeForSubmittedOnDate(Collection<LoanChargeData> chargesData, LoanScheduleAccrualData accrualData,
|
||||
LocalDate startDate, LocalDate endDate) {
|
||||
final Map<LoanChargeData, BigDecimal> applicableCharges = new HashMap<>();
|
||||
BigDecimal submittedDateFeeIncome = BigDecimal.ZERO;
|
||||
BigDecimal submittedDatePenaltyIncome = BigDecimal.ZERO;
|
||||
LocalDate scheduleEndDate = accrualData.getDueDateAsLocaldate();
|
||||
for (LoanChargeData loanCharge : chargesData) {
|
||||
BigDecimal chargeAmount = BigDecimal.ZERO;
|
||||
if (((accrualData.getInstallmentNumber() == 1 && DateUtils.isEqual(startDate, loanCharge.getSubmittedOnDate())
|
||||
&& DateUtils.isEqual(startDate, loanCharge.getDueDate())) || DateUtils.isBefore(startDate, loanCharge.getDueDate()))
|
||||
&& !DateUtils.isBefore(endDate, loanCharge.getSubmittedOnDate())
|
||||
&& !DateUtils.isBefore(scheduleEndDate, loanCharge.getDueDate())) {
|
||||
chargeAmount = loanCharge.getAmount();
|
||||
if (loanCharge.getAmountUnrecognized() != null) {
|
||||
chargeAmount = chargeAmount.subtract(loanCharge.getAmountUnrecognized());
|
||||
}
|
||||
boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) > 0;
|
||||
if (canAddCharge && (loanCharge.getAmountAccrued() == null || chargeAmount.compareTo(loanCharge.getAmountAccrued()) != 0)) {
|
||||
BigDecimal amountForAccrual = chargeAmount;
|
||||
if (loanCharge.getAmountAccrued() != null) {
|
||||
amountForAccrual = chargeAmount.subtract(loanCharge.getAmountAccrued());
|
||||
}
|
||||
applicableCharges.put(loanCharge, amountForAccrual);
|
||||
|
||||
}
|
||||
}
|
||||
if (loanCharge.isPenalty()) {
|
||||
submittedDatePenaltyIncome = submittedDatePenaltyIncome.add(chargeAmount);
|
||||
} else {
|
||||
submittedDateFeeIncome = submittedDateFeeIncome.add(chargeAmount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (submittedDateFeeIncome.compareTo(BigDecimal.ZERO) == 0) {
|
||||
submittedDateFeeIncome = null;
|
||||
}
|
||||
|
||||
if (submittedDatePenaltyIncome.compareTo(BigDecimal.ZERO) == 0) {
|
||||
submittedDatePenaltyIncome = null;
|
||||
}
|
||||
|
||||
accrualData.updateChargeDetails(applicableCharges, submittedDateFeeIncome, submittedDatePenaltyIncome);
|
||||
}
|
||||
|
||||
private void updateChargeForDueDate(Collection<LoanChargeData> chargesData, LoanScheduleAccrualData accrualData, LocalDate startDate,
|
||||
LocalDate endDate) {
|
||||
final Map<LoanChargeData, BigDecimal> applicableCharges = new HashMap<>();
|
||||
BigDecimal dueDateFeeIncome = BigDecimal.ZERO;
|
||||
BigDecimal dueDatePenaltyIncome = BigDecimal.ZERO;
|
||||
for (LoanChargeData loanCharge : chargesData) {
|
||||
BigDecimal chargeAmount = BigDecimal.ZERO;
|
||||
if (loanCharge.getDueDate() == null) {
|
||||
if (loanCharge.isInstallmentFee() && DateUtils.isEqual(endDate, accrualData.getDueDateAsLocaldate())) {
|
||||
Collection<LoanInstallmentChargeData> installmentData = loanCharge.getInstallmentChargeData();
|
||||
for (LoanInstallmentChargeData installmentChargeData : installmentData) {
|
||||
|
||||
if (installmentChargeData.getInstallmentNumber().equals(accrualData.getInstallmentNumber())) {
|
||||
BigDecimal accruableForInstallment = installmentChargeData.getAmount();
|
||||
if (installmentChargeData.getAmountUnrecognized() != null) {
|
||||
accruableForInstallment = accruableForInstallment.subtract(installmentChargeData.getAmountUnrecognized());
|
||||
}
|
||||
chargeAmount = accruableForInstallment;
|
||||
boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) > 0;
|
||||
if (canAddCharge && (installmentChargeData.getAmountAccrued() == null
|
||||
|| chargeAmount.compareTo(installmentChargeData.getAmountAccrued()) != 0)) {
|
||||
BigDecimal amountForAccrual = chargeAmount;
|
||||
if (installmentChargeData.getAmountAccrued() != null) {
|
||||
amountForAccrual = chargeAmount.subtract(installmentChargeData.getAmountAccrued());
|
||||
}
|
||||
applicableCharges.put(loanCharge, amountForAccrual);
|
||||
BigDecimal amountAccrued = chargeAmount;
|
||||
if (loanCharge.getAmountAccrued() != null) {
|
||||
amountAccrued = amountAccrued.add(loanCharge.getAmountAccrued());
|
||||
}
|
||||
loanCharge.updateAmountAccrued(amountAccrued);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (((accrualData.getInstallmentNumber() == 1 && DateUtils.isEqual(loanCharge.getDueDate(), startDate))
|
||||
|| DateUtils.isAfter(loanCharge.getDueDate(), startDate)) && !DateUtils.isAfter(loanCharge.getDueDate(), endDate)) {
|
||||
chargeAmount = loanCharge.getAmount();
|
||||
if (loanCharge.getAmountUnrecognized() != null) {
|
||||
chargeAmount = chargeAmount.subtract(loanCharge.getAmountUnrecognized());
|
||||
}
|
||||
boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) > 0;
|
||||
if (canAddCharge && (loanCharge.getAmountAccrued() == null || chargeAmount.compareTo(loanCharge.getAmountAccrued()) != 0)) {
|
||||
BigDecimal amountForAccrual = chargeAmount;
|
||||
if (loanCharge.getAmountAccrued() != null) {
|
||||
amountForAccrual = chargeAmount.subtract(loanCharge.getAmountAccrued());
|
||||
}
|
||||
applicableCharges.put(loanCharge, amountForAccrual);
|
||||
}
|
||||
}
|
||||
|
||||
if (loanCharge.isPenalty()) {
|
||||
dueDatePenaltyIncome = dueDatePenaltyIncome.add(chargeAmount);
|
||||
} else {
|
||||
dueDateFeeIncome = dueDateFeeIncome.add(chargeAmount);
|
||||
}
|
||||
}
|
||||
|
||||
if (dueDateFeeIncome.compareTo(BigDecimal.ZERO) == 0) {
|
||||
dueDateFeeIncome = null;
|
||||
}
|
||||
|
||||
if (dueDatePenaltyIncome.compareTo(BigDecimal.ZERO) == 0) {
|
||||
dueDatePenaltyIncome = null;
|
||||
}
|
||||
|
||||
accrualData.updateChargeDetails(applicableCharges, dueDateFeeIncome, dueDatePenaltyIncome);
|
||||
}
|
||||
|
||||
private void updateInterestIncome(final LoanScheduleAccrualData accrualData,
|
||||
final Collection<LoanTransactionData> loanWaiverTransactions,
|
||||
final Collection<LoanSchedulePeriodData> loanSchedulePeriodDataList, final LocalDate tillDate) {
|
||||
|
||||
BigDecimal interestIncome = BigDecimal.ZERO;
|
||||
if (accrualData.getInterestIncome() != null) {
|
||||
interestIncome = accrualData.getInterestIncome();
|
||||
}
|
||||
if (accrualData.getWaivedInterestIncome() != null) {
|
||||
BigDecimal recognized = BigDecimal.ZERO;
|
||||
BigDecimal unrecognized = BigDecimal.ZERO;
|
||||
BigDecimal remainingAmt = BigDecimal.ZERO;
|
||||
Collection<LoanTransactionData> loanTransactionDatas = new ArrayList<>();
|
||||
|
||||
for (LoanTransactionData loanTransactionData : loanWaiverTransactions) {
|
||||
LocalDate transactionDate = loanTransactionData.getDate();
|
||||
if (!DateUtils.isAfter(transactionDate, accrualData.getFromDateAsLocaldate())
|
||||
|| (DateUtils.isAfter(transactionDate, accrualData.getFromDateAsLocaldate())
|
||||
&& !DateUtils.isAfter(transactionDate, accrualData.getDueDateAsLocaldate())
|
||||
&& !DateUtils.isAfter(transactionDate, tillDate))) {
|
||||
loanTransactionDatas.add(loanTransactionData);
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<LoanTransactionData> iterator = loanTransactionDatas.iterator();
|
||||
for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDataList) {
|
||||
if (MathUtil.isLessThanOrEqualZero(recognized) && MathUtil.isLessThanOrEqualZero(unrecognized) && iterator.hasNext()) {
|
||||
LoanTransactionData loanTransactionData = iterator.next();
|
||||
recognized = recognized.add(loanTransactionData.getInterestPortion());
|
||||
unrecognized = unrecognized.add(loanTransactionData.getUnrecognizedIncomePortion());
|
||||
}
|
||||
if (DateUtils.isBefore(loanSchedulePeriodData.getDueDate(), accrualData.getDueDateAsLocaldate())) {
|
||||
remainingAmt = remainingAmt.add(loanSchedulePeriodData.getInterestWaived());
|
||||
if (recognized.compareTo(remainingAmt) > 0) {
|
||||
recognized = recognized.subtract(remainingAmt);
|
||||
remainingAmt = BigDecimal.ZERO;
|
||||
} else {
|
||||
remainingAmt = remainingAmt.subtract(recognized);
|
||||
recognized = BigDecimal.ZERO;
|
||||
if (unrecognized.compareTo(remainingAmt) >= 0) {
|
||||
unrecognized = unrecognized.subtract(remainingAmt);
|
||||
remainingAmt = BigDecimal.ZERO;
|
||||
} else if (iterator.hasNext()) {
|
||||
remainingAmt = remainingAmt.subtract(unrecognized);
|
||||
unrecognized = BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal interestWaived = accrualData.getWaivedInterestIncome();
|
||||
if (interestWaived.compareTo(recognized) > 0) {
|
||||
interestIncome = interestIncome.subtract(interestWaived.subtract(recognized));
|
||||
}
|
||||
}
|
||||
|
||||
accrualData.updateAccruableIncome(interestIncome);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void addIncomeAndAccrualTransactions(Long loanId) throws LoanNotFoundException {
|
||||
if (loanId != null) {
|
||||
Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
if (loan == null) {
|
||||
throw new LoanNotFoundException(loanId);
|
||||
}
|
||||
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
loan.processIncomeTransactions();
|
||||
this.loanRepositoryWrapper.saveAndFlush(loan);
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
}
|
||||
}
|
||||
|
||||
private void postJournalEntries(final Loan loan, final List<Long> existingTransactionIds,
|
||||
final List<Long> existingReversedTransactionIds) {
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
boolean isAccountTransfer = false;
|
||||
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds,
|
||||
existingReversedTransactionIds, isAccountTransfer);
|
||||
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
||||
}
|
||||
}
|
||||
+1480
File diff suppressed because it is too large
Load Diff
+2
@@ -123,6 +123,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
private final LoanRepository loanRepository;
|
||||
private final GSIMReadPlatformService gsimReadPlatformService;
|
||||
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -625,6 +626,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
LocalDate recalculateFrom = null;
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO);
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
}
|
||||
|
||||
loan.adjustNetDisbursalAmount(loan.getProposedPrincipal());
|
||||
|
||||
+3
-1
@@ -134,6 +134,7 @@ public class LoanAssembler {
|
||||
private final LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler;
|
||||
private final LoanChargeMapper loanChargeMapper;
|
||||
private final LoanCollateralManagementMapper loanCollateralManagementMapper;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
public Loan assembleFrom(final Long accountId) {
|
||||
final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true);
|
||||
@@ -274,7 +275,7 @@ public class LoanAssembler {
|
||||
// TODO: review
|
||||
loanApplication.recalculateAllCharges();
|
||||
topUpLoanConfiguration(element, loanApplication);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loanApplication);
|
||||
return loanApplication;
|
||||
}
|
||||
|
||||
@@ -827,6 +828,7 @@ public class LoanAssembler {
|
||||
|
||||
final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query, false);
|
||||
loan.updateLoanSchedule(loanSchedule);
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
loan.recalculateAllCharges();
|
||||
}
|
||||
|
||||
|
||||
+15
-2
@@ -165,6 +165,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
|
||||
private final NoteRepository noteRepository;
|
||||
private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
private static boolean isPartOfThisInstallment(LoanCharge loanCharge, LoanRepaymentScheduleInstallment e) {
|
||||
return DateUtils.isAfter(loanCharge.getDueDate(), e.getFromDate()) && !DateUtils.isAfter(loanCharge.getDueDate(), e.getDueDate());
|
||||
@@ -290,7 +291,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && isAppliedOnBackDate
|
||||
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
}
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
@@ -532,6 +534,13 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge,
|
||||
externalId);
|
||||
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()
|
||||
&& DateUtils.isBefore(loanCharge.getDueLocalDate(), DateUtils.getBusinessLocalDate())) {
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
|
||||
}
|
||||
|
||||
this.loanTransactionRepository.saveAndFlush(waiveTransaction);
|
||||
this.loanRepositoryWrapper.save(loan);
|
||||
|
||||
@@ -809,7 +818,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && runInterestRecalculation
|
||||
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
}
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
}
|
||||
@@ -1140,6 +1150,9 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
ChangedTransactionDetail changedTransactionDetail = loan
|
||||
.handleRegenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
|
||||
if (changedTransactionDetail != null) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||
|
||||
+2
-3
@@ -25,14 +25,13 @@ import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainServiceJpa;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlatformService {
|
||||
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanAccountDomainServiceJpa loanAccountDomainService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
@PostConstruct
|
||||
public void addListeners() {
|
||||
@@ -47,7 +46,7 @@ public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlat
|
||||
log.debug("Loan Status change for loan {}", loan.getId());
|
||||
if (loan.getStatus().isClosedObligationsMet() || loan.getStatus().isOverpaid()) {
|
||||
log.debug("Loan Status {} for loan {}", loan.getStatus().getCode(), loan.getId());
|
||||
loanAccountDomainService.applyFinalIncomeAccrualTransaction(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForLoanClosure(loan);
|
||||
}
|
||||
if (loan.isOpen()) {
|
||||
loan.handleMaturityDateActivate();
|
||||
|
||||
+98
-11
@@ -260,6 +260,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private final LoanDownPaymentHandlerService loanDownPaymentHandlerService;
|
||||
private final AccountTransferRepository accountTransferRepository;
|
||||
private final LoanTransactionAssembler loanTransactionAssembler;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -474,6 +475,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null);
|
||||
}
|
||||
loan.adjustNetDisbursalAmount(amountToDisburse.getAmount());
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
|
||||
LocalDate firstInstallmentDueDate = loan.fetchRepaymentScheduleInstallment(1).getDueDate();
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()
|
||||
&& (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || loan.isDisbursementMissed())) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
if (disbursementTransaction != null) {
|
||||
loanTransactionRepository.saveAndFlush(disbursementTransaction);
|
||||
}
|
||||
@@ -513,6 +523,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
} else {
|
||||
loanDownPaymentHandlerService.handleDownPayment(scheduleGeneratorDTO, command, disbursementTransaction, loan);
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -559,7 +573,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
updateRecurringCalendarDatesForInterestRecalculation(loan);
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
// Post Dated Checks
|
||||
@@ -789,6 +804,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
} else {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null);
|
||||
}
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
|
||||
LocalDate firstInstallmentDueDate = loan.fetchRepaymentScheduleInstallment(1).getDueDate();
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()
|
||||
&& (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || loan.isDisbursementMissed())) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
}
|
||||
if (!changes.isEmpty()) {
|
||||
|
||||
@@ -834,7 +857,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
updateRecurringCalendarDatesForInterestRecalculation(loan);
|
||||
loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalBusinessEvent(loan));
|
||||
}
|
||||
@@ -892,6 +916,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final Map<String, Object> changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds,
|
||||
existingReversedTransactionIds);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
if (loan.isTopup() && loan.getClientId() != null) {
|
||||
final Long loanIdToClose = loan.getTopupLoanDetails().getLoanIdToClose();
|
||||
@@ -997,7 +1023,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, loan);
|
||||
}
|
||||
loan.processIncomeAccrualTransactionOnLoanClosure();
|
||||
}
|
||||
|
||||
private boolean doPostLoanTransactionChecks(final Loan loan, final LocalDate transactionDate,
|
||||
@@ -1117,6 +1142,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||
@@ -1188,7 +1215,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, newInterestPaymentWaiverTransaction.getTransactionDate());
|
||||
|
||||
// disable all active standing orders linked to this loan if status changes to closed
|
||||
@@ -1494,6 +1522,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
defaultLoanLifecycleStateMachine, transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
|
||||
scheduleGeneratorDTO, reversalTxnExternalId);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
boolean thereIsNewTransaction = newTransactionDetail.isGreaterThanZero(loan.getPrincipal().getCurrency());
|
||||
if (thereIsNewTransaction) {
|
||||
if (paymentDetail != null) {
|
||||
@@ -1549,7 +1582,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
@@ -1734,6 +1768,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction,
|
||||
defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
|
||||
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
this.loanTransactionRepository.saveAndFlush(waiveInterestTransaction);
|
||||
|
||||
/***
|
||||
@@ -1761,7 +1800,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
@@ -1825,6 +1865,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine, changes,
|
||||
existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
LoanTransaction writeOff = changedTransactionDetail.getNewTransactionMappings().remove(0L);
|
||||
this.loanTransactionRepository.saveAndFlush(writeOff);
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
@@ -1841,7 +1887,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(writeOff));
|
||||
@@ -1892,6 +1939,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine, changes,
|
||||
existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L);
|
||||
if (possibleClosingTransaction != null) {
|
||||
this.loanTransactionRepository.saveAndFlush(possibleClosingTransaction);
|
||||
@@ -1913,7 +1966,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
@@ -2366,6 +2420,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
holidays, workingDays, isSkipRepaymentOnFirstMonth, numberOfDays);
|
||||
}
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueCalendarChangeBusinessEvent(loan));
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
@@ -2554,6 +2612,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(defaultLoanLifecycleStateMachine, existingTransactionIds,
|
||||
existingReversedTransactionIds, scheduleGeneratorDTO);
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
if (changedTransactionDetail != null) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||
@@ -2566,7 +2628,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
if (writeOffTransaction != null) {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoWrittenOffBusinessEvent(writeOffTransaction));
|
||||
@@ -2702,6 +2765,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
}
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
if (command.entityId() != null && changedTransactionDetail != null) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||
@@ -2716,7 +2784,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
this.loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
@@ -2761,6 +2830,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO,
|
||||
existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
if (changedTransactionDetail != null) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||
@@ -2772,7 +2846,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccountDomainService.recalculateAccruals(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan));
|
||||
}
|
||||
|
||||
@@ -2812,6 +2887,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedEmiAmountParameterName);
|
||||
loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount,
|
||||
nextPossibleRepaymentDate, rescheduledRepaymentDate);
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private List<LoanRepaymentScheduleInstallment> retrieveRepaymentScheduleFromModel(LoanScheduleModel model) {
|
||||
@@ -2984,6 +3065,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final Map<String, Object> changes = loan.undoLastDisbursal(scheduleGeneratorDTO, existingTransactionIds,
|
||||
existingReversedTransactionIds, loan);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
|
||||
}
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
String noteText;
|
||||
|
||||
+14
-39
@@ -36,7 +36,6 @@ import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
|
||||
import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
|
||||
import org.apache.fineract.organisation.holiday.domain.HolidayRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepository;
|
||||
import org.apache.fineract.organisation.staff.domain.StaffRepository;
|
||||
import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
|
||||
import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator;
|
||||
@@ -67,7 +66,6 @@ import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainServiceJpa;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
|
||||
@@ -96,12 +94,9 @@ import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlat
|
||||
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformServiceJpaRepositoryImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService;
|
||||
@@ -170,13 +165,6 @@ public class LoanAccountConfiguration {
|
||||
return new GLIMAccountInfoWritePlatformServiceImpl(glimAccountRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanAccrualPlatformService.class)
|
||||
public LoanAccrualPlatformService loanAccrualPlatformService(LoanReadPlatformService loanReadPlatformService,
|
||||
LoanAccrualWritePlatformService loanAccrualWritePlatformService) {
|
||||
return new LoanAccrualPlatformServiceImpl(loanReadPlatformService, loanAccrualWritePlatformService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanAccrualTransactionBusinessEventService.class)
|
||||
public LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService(
|
||||
@@ -185,22 +173,6 @@ public class LoanAccountConfiguration {
|
||||
return new LoanAccrualTransactionBusinessEventServiceImpl(businessEventNotifierService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanAccrualWritePlatformService.class)
|
||||
public LoanAccrualWritePlatformService loanAccrualWritePlatformService(LoanReadPlatformService loanReadPlatformService,
|
||||
LoanChargeReadPlatformService loanChargeReadPlatformService, JdbcTemplate jdbcTemplate,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator, JournalEntryWritePlatformService journalEntryWritePlatformService,
|
||||
PlatformSecurityContext context, LoanRepositoryWrapper loanRepositoryWrapper, LoanRepository loanRepository,
|
||||
OfficeRepository officeRepository, BusinessEventNotifierService businessEventNotifierService,
|
||||
LoanTransactionRepository loanTransactionRepository,
|
||||
LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService,
|
||||
ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory) {
|
||||
return new LoanAccrualWritePlatformServiceImpl(loanReadPlatformService, loanChargeReadPlatformService, jdbcTemplate, sqlGenerator,
|
||||
journalEntryWritePlatformService, context, loanRepositoryWrapper, loanRepository, officeRepository,
|
||||
businessEventNotifierService, loanTransactionRepository, loanAccrualTransactionBusinessEventService,
|
||||
configurationDomainService, externalIdFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanApplicationWritePlatformService.class)
|
||||
public LoanApplicationWritePlatformService loanApplicationWritePlatformService(PlatformSecurityContext context,
|
||||
@@ -215,13 +187,13 @@ public class LoanAccountConfiguration {
|
||||
LoanUtilService loanUtilService, CalendarReadPlatformService calendarReadPlatformService,
|
||||
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, GLIMAccountInfoRepository glimRepository,
|
||||
LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService,
|
||||
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine) {
|
||||
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccrualsProcessingService loanAccrualsProcessingService) {
|
||||
return new LoanApplicationWritePlatformServiceJpaRepositoryImpl(context, loanApplicationTransitionValidator,
|
||||
loanApplicationValidator, loanRepositoryWrapper, noteRepository, loanAssembler, loanSummaryWrapper,
|
||||
loanRepaymentScheduleTransactionProcessorFactory, calendarRepository, calendarInstanceRepository, savingsAccountRepository,
|
||||
accountAssociationsRepository, businessEventNotifierService, loanScheduleAssembler, loanUtilService,
|
||||
calendarReadPlatformService, entityDatatableChecksWritePlatformService, glimRepository, loanRepository,
|
||||
gsimReadPlatformService, defaultLoanLifecycleStateMachine);
|
||||
gsimReadPlatformService, defaultLoanLifecycleStateMachine, loanAccrualsProcessingService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -246,13 +218,14 @@ public class LoanAccountConfiguration {
|
||||
AccountNumberGenerator accountNumberGenerator, GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService,
|
||||
LoanCollateralAssembler loanCollateralAssembler, LoanScheduleCalculationPlatformService calculationPlatformService,
|
||||
LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler, LoanChargeMapper loanChargeMapper,
|
||||
LoanCollateralManagementMapper loanCollateralManagementMapper) {
|
||||
LoanCollateralManagementMapper loanCollateralManagementMapper, LoanAccrualsProcessingService loanAccrualsProcessingService) {
|
||||
return new LoanAssembler(fromApiJsonHelper, loanRepository, loanProductRepository, clientRepository, groupRepository,
|
||||
fundRepository, staffRepository, codeValueRepository, loanScheduleAssembler, loanChargeAssembler, collateralAssembler,
|
||||
loanSummaryWrapper, loanRepaymentScheduleTransactionProcessorFactory, holidayRepository, configurationDomainService,
|
||||
workingDaysRepository, rateAssembler, defaultLoanLifecycleStateMachine, externalIdFactory, accountNumberFormatRepository,
|
||||
glimRepository, accountNumberGenerator, glimAccountInfoWritePlatformService, loanCollateralAssembler,
|
||||
calculationPlatformService, loanDisbursementDetailsAssembler, loanChargeMapper, loanCollateralManagementMapper);
|
||||
calculationPlatformService, loanDisbursementDetailsAssembler, loanChargeMapper, loanCollateralManagementMapper,
|
||||
loanAccrualsProcessingService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -309,7 +282,8 @@ public class LoanAccountConfiguration {
|
||||
ExternalIdFactory externalIdFactory, AccountTransferDetailRepository accountTransferDetailRepository,
|
||||
LoanChargeAssembler loanChargeAssembler, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService,
|
||||
PaymentDetailWritePlatformService paymentDetailWritePlatformService, NoteRepository noteRepository,
|
||||
LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService
|
||||
LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService,
|
||||
LoanAccrualsProcessingService loanAccrualsProcessingService
|
||||
|
||||
) {
|
||||
return new LoanChargeWritePlatformServiceImpl(loanChargeApiJsonValidator, loanAssembler, chargeRepository,
|
||||
@@ -318,7 +292,8 @@ public class LoanAccountConfiguration {
|
||||
loanChargeReadPlatformService, defaultLoanLifecycleStateMachine, accountAssociationsReadPlatformService, fromApiJsonHelper,
|
||||
configurationDomainService, loanRepaymentScheduleTransactionProcessorFactory, externalIdFactory,
|
||||
accountTransferDetailRepository, loanChargeAssembler, replayedTransactionBusinessEventService,
|
||||
paymentDetailWritePlatformService, noteRepository, loanAccrualTransactionBusinessEventService);
|
||||
paymentDetailWritePlatformService, noteRepository, loanAccrualTransactionBusinessEventService,
|
||||
loanAccrualsProcessingService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -354,8 +329,8 @@ public class LoanAccountConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanStatusChangePlatformService.class)
|
||||
public LoanStatusChangePlatformService loanStatusChangePlatformService(BusinessEventNotifierService businessEventNotifierService,
|
||||
LoanAccountDomainServiceJpa loanAccountDomainService) {
|
||||
return new LoanStatusChangePlatformServiceImpl(businessEventNotifierService, loanAccountDomainService);
|
||||
LoanAccrualsProcessingService loanAccrualsProcessingService) {
|
||||
return new LoanStatusChangePlatformServiceImpl(businessEventNotifierService, loanAccrualsProcessingService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -397,7 +372,7 @@ public class LoanAccountConfiguration {
|
||||
ExternalIdFactory externalIdFactory, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService,
|
||||
LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService, ErrorHandler errorHandler,
|
||||
LoanDownPaymentHandlerService loanDownPaymentHandlerService, AccountTransferRepository accountTransferRepository,
|
||||
LoanTransactionAssembler loanTransactionAssembler) {
|
||||
LoanTransactionAssembler loanTransactionAssembler, LoanAccrualsProcessingService loanAccrualsProcessingService) {
|
||||
return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer,
|
||||
loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository,
|
||||
loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository,
|
||||
@@ -410,7 +385,7 @@ public class LoanAccountConfiguration {
|
||||
repaymentWithPostDatedChecksAssembler, postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository,
|
||||
defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService,
|
||||
loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService, accountTransferRepository,
|
||||
loanTransactionAssembler);
|
||||
loanTransactionAssembler, loanAccrualsProcessingService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+6
-6
@@ -40,7 +40,7 @@ import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -54,7 +54,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
public class AddPeriodicAccrualEntriesBusinessStepTest {
|
||||
|
||||
@Mock
|
||||
private LoanAccrualPlatformService loanAccrualPlatformService;
|
||||
private LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
|
||||
private AddPeriodicAccrualEntriesBusinessStep underTest;
|
||||
|
||||
@@ -64,7 +64,7 @@ public class AddPeriodicAccrualEntriesBusinessStepTest {
|
||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||
ThreadLocalContextUtil
|
||||
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
|
||||
underTest = new AddPeriodicAccrualEntriesBusinessStep(loanAccrualPlatformService);
|
||||
underTest = new AddPeriodicAccrualEntriesBusinessStep(loanAccrualsProcessingService);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -79,7 +79,7 @@ public class AddPeriodicAccrualEntriesBusinessStepTest {
|
||||
// when
|
||||
final Loan processedLoan = underTest.execute(loanForProcessing);
|
||||
// then
|
||||
verify(loanAccrualPlatformService, times(1)).addPeriodicAccruals(any(LocalDate.class), eq(loanForProcessing));
|
||||
verify(loanAccrualsProcessingService, times(1)).addPeriodicAccruals(any(LocalDate.class), eq(loanForProcessing));
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
}
|
||||
|
||||
@@ -89,13 +89,13 @@ public class AddPeriodicAccrualEntriesBusinessStepTest {
|
||||
final Long loanId = RandomUtils.nextLong();
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(loanId);
|
||||
doThrow(new MultiException(Collections.singletonList(new RuntimeException()))).when(loanAccrualPlatformService)
|
||||
doThrow(new MultiException(Collections.singletonList(new RuntimeException()))).when(loanAccrualsProcessingService)
|
||||
.addPeriodicAccruals(any(LocalDate.class), eq(loanForProcessing));
|
||||
// when
|
||||
final BusinessStepException businessStepException = Assert.assertThrows(BusinessStepException.class,
|
||||
() -> underTest.execute(loanForProcessing));
|
||||
// then
|
||||
verify(loanAccrualPlatformService, times(1)).addPeriodicAccruals(any(LocalDate.class), eq(loanForProcessing));
|
||||
verify(loanAccrualsProcessingService, times(1)).addPeriodicAccruals(any(LocalDate.class), eq(loanForProcessing));
|
||||
assertEquals(String.format("Fail to process period accrual for loan id [%s]", loanId), businessStepException.getMessage());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user