FINERACT-2107: Interest Refund - Allocation, Business Event & Full Repayment
This commit is contained in:
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.event.business.domain.loan.transaction;
|
||||||
|
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
|
||||||
|
public class LoanTransactionInterestRefundPostBusinessEvent extends LoanTransactionBusinessEvent {
|
||||||
|
|
||||||
|
private static final String TYPE = "LoanTransactionInterestRefundPostBusinessEvent";
|
||||||
|
|
||||||
|
public LoanTransactionInterestRefundPostBusinessEvent(LoanTransaction value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.event.business.domain.loan.transaction;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
|
||||||
|
public class LoanTransactionInterestRefundPreBusinessEvent extends LoanBusinessEvent {
|
||||||
|
|
||||||
|
private static final String TYPE = "LoanTransactionInterestRefundPreBusinessEvent";
|
||||||
|
|
||||||
|
public LoanTransactionInterestRefundPreBusinessEvent(Loan value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-1
@@ -1718,6 +1718,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
public List<Long> findExistingTransactionIds() {
|
public List<Long> findExistingTransactionIds() {
|
||||||
return getLoanTransactions().stream() //
|
return getLoanTransactions().stream() //
|
||||||
|
.filter(loanTransaction -> loanTransaction.getId() != null) //
|
||||||
.map(LoanTransaction::getId) //
|
.map(LoanTransaction::getId) //
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
@@ -1725,6 +1726,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
public List<Long> findExistingReversedTransactionIds() {
|
public List<Long> findExistingReversedTransactionIds() {
|
||||||
return getLoanTransactions().stream() //
|
return getLoanTransactions().stream() //
|
||||||
.filter(LoanTransaction::isReversed) //
|
.filter(LoanTransaction::isReversed) //
|
||||||
|
.filter(loanTransaction -> loanTransaction.getId() != null) //
|
||||||
.map(LoanTransaction::getId) //
|
.map(LoanTransaction::getId) //
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
@@ -2256,7 +2258,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
private void validateRepaymentTypeAccountStatus(LoanTransaction repaymentTransaction, LoanEvent event) {
|
private void validateRepaymentTypeAccountStatus(LoanTransaction repaymentTransaction, LoanEvent event) {
|
||||||
if (repaymentTransaction.isGoodwillCredit() || repaymentTransaction.isInterestPaymentWaiver()
|
if (repaymentTransaction.isGoodwillCredit() || repaymentTransaction.isInterestPaymentWaiver()
|
||||||
|| repaymentTransaction.isMerchantIssuedRefund() || repaymentTransaction.isPayoutRefund()
|
|| repaymentTransaction.isMerchantIssuedRefund() || repaymentTransaction.isPayoutRefund()
|
||||||
|| repaymentTransaction.isChargeRefund() || repaymentTransaction.isRepayment() || repaymentTransaction.isDownPayment()) {
|
|| repaymentTransaction.isChargeRefund() || repaymentTransaction.isRepayment() || repaymentTransaction.isDownPayment()
|
||||||
|
|| repaymentTransaction.isInterestRefund()) {
|
||||||
|
|
||||||
if (!(isOpen() || isClosedObligationsMet() || isOverPaid())) {
|
if (!(isOpen() || isClosedObligationsMet() || isOverPaid())) {
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||||
|
|||||||
+10
-1
@@ -189,6 +189,11 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
chargeRefundChargeType);
|
chargeRefundChargeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LoanTransaction interestRefund(final Loan loan, final BigDecimal amount, final LocalDate date,
|
||||||
|
final ExternalId externalId) {
|
||||||
|
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.INTEREST_REFUND, null, amount, date, externalId);
|
||||||
|
}
|
||||||
|
|
||||||
public static LoanTransaction chargeAdjustment(final Loan loan, final BigDecimal amount, final LocalDate transactionDate,
|
public static LoanTransaction chargeAdjustment(final Loan loan, final BigDecimal amount, final LocalDate transactionDate,
|
||||||
final ExternalId externalId, PaymentDetail paymentDetail) {
|
final ExternalId externalId, PaymentDetail paymentDetail) {
|
||||||
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_ADJUSTMENT, paymentDetail, amount, transactionDate,
|
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_ADJUSTMENT, paymentDetail, amount, transactionDate,
|
||||||
@@ -596,7 +601,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
|
|
||||||
public boolean isRepaymentLikeType() {
|
public boolean isRepaymentLikeType() {
|
||||||
return isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isChargeRefund()
|
return isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isChargeRefund()
|
||||||
|| isChargeAdjustment() || isDownPayment() || isInterestPaymentWaiver();
|
|| isChargeAdjustment() || isDownPayment() || isInterestPaymentWaiver() || isInterestRefund();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTypeAllowedForChargeback() {
|
public boolean isTypeAllowedForChargeback() {
|
||||||
@@ -1118,6 +1123,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
return MathUtil.isGreaterThanZero(overPaymentPortion);
|
return MathUtil.isGreaterThanZero(overPaymentPortion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInterestRefund() {
|
||||||
|
return getTypeOf().isInterestRefund();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO missing hashCode(), equals(Object obj), but probably OK as long as
|
// TODO missing hashCode(), equals(Object obj), but probably OK as long as
|
||||||
// this is never stored in a Collection.
|
// this is never stored in a Collection.
|
||||||
}
|
}
|
||||||
|
|||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
|
||||||
|
public interface InterestRefundService {
|
||||||
|
|
||||||
|
boolean canHandle(Loan loan);
|
||||||
|
|
||||||
|
BigDecimal calculateInterestRefundAmount(Long loanId, BigDecimal relatedRefundTransactionAmount,
|
||||||
|
LocalDate relatedRefundTransactionDate);
|
||||||
|
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 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.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class InterestRefundServiceDelegate {
|
||||||
|
|
||||||
|
private final List<InterestRefundService> interestRefundService;
|
||||||
|
|
||||||
|
public InterestRefundService lookupInterestRefundService(final Loan loan) {
|
||||||
|
return interestRefundService.stream().filter(iRS -> iRS.canHandle(loan)).findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-3
@@ -96,6 +96,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
|||||||
public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
|
public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
|
||||||
|
|
||||||
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy";
|
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy";
|
||||||
|
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY_NAME = "Advanced payment allocation strategy";
|
||||||
|
|
||||||
public final EMICalculator emiCalculator;
|
public final EMICalculator emiCalculator;
|
||||||
|
|
||||||
@@ -106,7 +107,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Advanced payment allocation strategy";
|
return ADVANCED_PAYMENT_ALLOCATION_STRATEGY_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -232,8 +233,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
case CHARGEBACK -> handleChargeback(loanTransaction, ctx);
|
case CHARGEBACK -> handleChargeback(loanTransaction, ctx);
|
||||||
case CREDIT_BALANCE_REFUND ->
|
case CREDIT_BALANCE_REFUND ->
|
||||||
handleCreditBalanceRefund(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getOverpaymentHolder());
|
handleCreditBalanceRefund(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getOverpaymentHolder());
|
||||||
case REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND, GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT, DOWN_PAYMENT,
|
case INTEREST_REFUND, REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND, GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT,
|
||||||
WAIVE_INTEREST, RECOVERY_REPAYMENT, INTEREST_PAYMENT_WAIVER ->
|
DOWN_PAYMENT, WAIVE_INTEREST, RECOVERY_REPAYMENT, INTEREST_PAYMENT_WAIVER ->
|
||||||
handleRepayment(loanTransaction, ctx);
|
handleRepayment(loanTransaction, ctx);
|
||||||
case CHARGE_OFF -> handleChargeOff(loanTransaction, ctx);
|
case CHARGE_OFF -> handleChargeOff(loanTransaction, ctx);
|
||||||
case CHARGE_PAYMENT -> handleChargePayment(loanTransaction, ctx);
|
case CHARGE_PAYMENT -> handleChargePayment(loanTransaction, ctx);
|
||||||
|
|||||||
+49
-2
@@ -55,6 +55,8 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction
|
|||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionGoodwillCreditPreBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionGoodwillCreditPreBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPreBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPreBusinessEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestRefundPostBusinessEvent;
|
||||||
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestRefundPreBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMakeRepaymentPostBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMakeRepaymentPostBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMakeRepaymentPreBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMakeRepaymentPreBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMerchantIssuedRefundPostBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionMerchantIssuedRefundPostBusinessEvent;
|
||||||
@@ -89,11 +91,14 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundServiceDelegate;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService;
|
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
|
||||||
import org.apache.fineract.portfolio.note.domain.Note;
|
import org.apache.fineract.portfolio.note.domain.Note;
|
||||||
import org.apache.fineract.portfolio.note.domain.NoteRepository;
|
import org.apache.fineract.portfolio.note.domain.NoteRepository;
|
||||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||||
@@ -133,6 +138,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
|
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
|
||||||
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
||||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||||
|
private final InterestRefundServiceDelegate interestRefundServiceDelegate;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -159,6 +165,26 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
this.loanCollateralManagementRepository.saveAll(loanCollateralManagementSet);
|
this.loanCollateralManagementRepository.saveAll(loanCollateralManagementSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public LoanTransaction createInterestRefundLoanTransaction(Loan loan, final LocalDate transactionDate,
|
||||||
|
BigDecimal relatedRefundTransactionAmount) {
|
||||||
|
InterestRefundService interestRefundService = interestRefundServiceDelegate.lookupInterestRefundService(loan);
|
||||||
|
if (interestRefundService == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
BigDecimal interestRefundAmount = interestRefundService.calculateInterestRefundAmount(loan.getId(), relatedRefundTransactionAmount,
|
||||||
|
transactionDate);
|
||||||
|
|
||||||
|
final ExternalId txnExternalId = externalIdFactory.create();
|
||||||
|
|
||||||
|
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestRefundPreBusinessEvent(loan));
|
||||||
|
|
||||||
|
LoanTransaction newInterestRefundTransaction;
|
||||||
|
newInterestRefundTransaction = LoanTransaction.interestRefund(loan, interestRefundAmount, transactionDate, txnExternalId);
|
||||||
|
|
||||||
|
return newInterestRefundTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public LoanTransaction makeRepayment(final LoanTransactionType repaymentTransactionType, Loan loan, final LocalDate transactionDate,
|
public LoanTransaction makeRepayment(final LoanTransactionType repaymentTransactionType, Loan loan, final LocalDate transactionDate,
|
||||||
@@ -199,6 +225,18 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom,
|
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom,
|
||||||
holidayDetailDto);
|
holidayDetailDto);
|
||||||
|
|
||||||
|
boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
|
||||||
|
.map(LoanSupportedInterestRefundTypes::getTransactionType)
|
||||||
|
.anyMatch(transactionType -> transactionType.equals(repaymentTransactionType));
|
||||||
|
LoanTransaction newInterestRefundTransaction = null;
|
||||||
|
|
||||||
|
if (shouldCreateInterestRefundTransaction) {
|
||||||
|
newInterestRefundTransaction = createInterestRefundLoanTransaction(loan, transactionDate, transactionAmount);
|
||||||
|
if (newInterestRefundTransaction != null) {
|
||||||
|
loan.addLoanTransaction(newInterestRefundTransaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
|
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
|
||||||
defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
|
defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
|
||||||
scheduleGeneratorDTO, isHolidayValidationDone);
|
scheduleGeneratorDTO, isHolidayValidationDone);
|
||||||
@@ -209,6 +247,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
|
saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
|
||||||
|
if (newInterestRefundTransaction != null) {
|
||||||
|
saveLoanTransactionWithDataIntegrityViolationChecks(newInterestRefundTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the
|
* TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the
|
||||||
@@ -243,7 +284,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(transactionRepaymentEvent);
|
businessEventNotifierService.notifyPostBusinessEvent(transactionRepaymentEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// disable all active standing orders linked to this loan if status
|
// disable all active standing orders linked to this loan if status
|
||||||
// changes to closed
|
// changes to closed
|
||||||
disableStandingInstructionsLinkedToClosedLoan(loan);
|
disableStandingInstructionsLinkedToClosedLoan(loan);
|
||||||
@@ -276,7 +316,10 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (shouldCreateInterestRefundTransaction && newInterestRefundTransaction != null) {
|
||||||
|
businessEventNotifierService
|
||||||
|
.notifyPostBusinessEvent(new LoanTransactionInterestRefundPostBusinessEvent(newInterestRefundTransaction));
|
||||||
|
}
|
||||||
return newRepaymentTransaction;
|
return newRepaymentTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +342,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
repaymentEvent = new LoanTransactionRecoveryPaymentPreBusinessEvent(loan);
|
repaymentEvent = new LoanTransactionRecoveryPaymentPreBusinessEvent(loan);
|
||||||
} else if (repaymentTransactionType.isDownPayment()) {
|
} else if (repaymentTransactionType.isDownPayment()) {
|
||||||
repaymentEvent = new LoanTransactionDownPaymentPreBusinessEvent(loan);
|
repaymentEvent = new LoanTransactionDownPaymentPreBusinessEvent(loan);
|
||||||
|
} else if (repaymentTransactionType.isInterestRefund()) {
|
||||||
|
repaymentEvent = new LoanTransactionInterestRefundPreBusinessEvent(loan);
|
||||||
}
|
}
|
||||||
return repaymentEvent;
|
return repaymentEvent;
|
||||||
}
|
}
|
||||||
@@ -322,6 +367,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
repaymentEvent = new LoanTransactionRecoveryPaymentPostBusinessEvent(transaction);
|
repaymentEvent = new LoanTransactionRecoveryPaymentPostBusinessEvent(transaction);
|
||||||
} else if (repaymentTransactionType.isDownPayment()) {
|
} else if (repaymentTransactionType.isDownPayment()) {
|
||||||
repaymentEvent = new LoanTransactionDownPaymentPostBusinessEvent(transaction);
|
repaymentEvent = new LoanTransactionDownPaymentPostBusinessEvent(transaction);
|
||||||
|
} else if (repaymentTransactionType.isInterestRefund()) {
|
||||||
|
repaymentEvent = new LoanTransactionInterestRefundPostBusinessEvent(transaction);
|
||||||
}
|
}
|
||||||
return repaymentEvent;
|
return repaymentEvent;
|
||||||
}
|
}
|
||||||
|
|||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* 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.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.starter.AdvancedPaymentScheduleTransactionProcessorCondition;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
||||||
|
import org.springframework.context.annotation.Conditional;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
|
||||||
|
@Service
|
||||||
|
public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundService {
|
||||||
|
|
||||||
|
private final AdvancedPaymentScheduleTransactionProcessor processor;
|
||||||
|
private final EMICalculator emiCalculator;
|
||||||
|
private final LoanAssembler loanAssembler;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canHandle(Loan loan) {
|
||||||
|
return loan != null && loan.isInterestBearing() && processor.accept(loan.getTransactionProcessingStrategyCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean omitDisbursements(LoanTransaction lt, final AtomicReference<BigDecimal> refundFinal) {
|
||||||
|
if (lt.getTypeOf().isDisbursement() && refundFinal.get().compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
if (lt.getAmount().compareTo(refundFinal.get()) <= 0) {
|
||||||
|
refundFinal.set(refundFinal.get().subtract(lt.getAmount()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LoanTransaction calculateReducedAmountDisbursements(LoanTransaction lt, final AtomicReference<BigDecimal> refundFinal) {
|
||||||
|
if (lt.getTypeOf().isDisbursement() && refundFinal.get().compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
LoanTransaction result = new LoanTransaction(lt.getLoan(), lt.getLoan().getOffice(), lt.getTypeOf().getValue(), lt.getDateOf(),
|
||||||
|
lt.getAmount().subtract(refundFinal.get()), lt.getPrincipalPortion(), lt.getInterestPortion(),
|
||||||
|
lt.getFeeChargesPortion(), lt.getPenaltyChargesPortion(),
|
||||||
|
lt.getOverPaymentPortion(lt.getLoan().getCurrency()).getAmount(), lt.isReversed(), lt.getPaymentDetail(),
|
||||||
|
lt.getExternalId());
|
||||||
|
refundFinal.set(BigDecimal.ZERO);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return lt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal totalInterest(final Loan loan, BigDecimal refundAmount, LocalDate relatedRefundTransactionDate) {
|
||||||
|
final AtomicReference<BigDecimal> refundFinal = new AtomicReference<>(refundAmount);
|
||||||
|
List<LoanTransaction> transactionsToReprocess = loan.getLoanTransactions().stream().filter(lt -> !lt.isReversed()) //
|
||||||
|
.filter(lt -> !lt.isAccrual() && !lt.isAccrualActivity()) //
|
||||||
|
.filter(lt -> omitDisbursements(lt, refundFinal)) //
|
||||||
|
.map(lt -> calculateReducedAmountDisbursements(lt, refundFinal)).toList();
|
||||||
|
|
||||||
|
List<LoanRepaymentScheduleInstallment> installmentsToReprocess = new ArrayList<>(
|
||||||
|
loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isReAged() && !i.isAdditional()).toList());
|
||||||
|
|
||||||
|
ProgressiveLoanInterestScheduleModel modelAfter = processor.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(),
|
||||||
|
transactionsToReprocess, loan.getCurrency(), installmentsToReprocess, loan.getActiveCharges()).getRight();
|
||||||
|
BigDecimal payableInterest = BigDecimal.ZERO;
|
||||||
|
if (modelAfter != null && loan.getStatus().isActive()) {
|
||||||
|
LoanRepaymentScheduleInstallment actualInstallment = loan.getRelatedRepaymentScheduleInstallment(relatedRefundTransactionDate);
|
||||||
|
if (actualInstallment == null) {
|
||||||
|
actualInstallment = loan.getLastLoanRepaymentScheduleInstallment();
|
||||||
|
}
|
||||||
|
payableInterest = emiCalculator.getPayableDetails(modelAfter, actualInstallment.getDueDate(), relatedRefundTransactionDate)
|
||||||
|
.getPayableInterest().getAmount();
|
||||||
|
}
|
||||||
|
BigDecimal paidInterest = installmentsToReprocess.stream().map(i -> i.getInterestPaid(loan.getCurrency())).filter(Objects::nonNull)
|
||||||
|
.map(Money::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
return payableInterest.add(paidInterest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
|
||||||
|
public BigDecimal calculateInterestRefundAmount(Long loanId, BigDecimal relatedRefundTransactionAmount,
|
||||||
|
LocalDate relatedRefundTransactionDate) {
|
||||||
|
Loan loan = loanAssembler.assembleFrom(loanId);
|
||||||
|
BigDecimal totalInterestBeforeRefund = totalInterest(loan, BigDecimal.ZERO, relatedRefundTransactionDate);
|
||||||
|
BigDecimal totalInterestAfterRefund = totalInterest(loan, relatedRefundTransactionAmount, relatedRefundTransactionDate);
|
||||||
|
return totalInterestBeforeRefund.subtract(totalInterestAfterRefund);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -169,4 +169,5 @@
|
|||||||
<include file="parts/0148_transaction_summary_with_asset_owner_report_performance_improvement.xml" relativeToChangelogFile="true" />
|
<include file="parts/0148_transaction_summary_with_asset_owner_report_performance_improvement.xml" relativeToChangelogFile="true" />
|
||||||
<include file="parts/0149_update_global_configuration_names.xml" relativeToChangelogFile="true" />
|
<include file="parts/0149_update_global_configuration_names.xml" relativeToChangelogFile="true" />
|
||||||
<include file="parts/0150_transaction_summary_with_asset_owner_report_interest_waiver_interest_refund_added.xml" relativeToChangelogFile="true" />
|
<include file="parts/0150_transaction_summary_with_asset_owner_report_interest_waiver_interest_refund_added.xml" relativeToChangelogFile="true" />
|
||||||
|
<include file="parts/0151_interest_refund_business_events.xml" relativeToChangelogFile="true" />
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
|
||||||
|
<changeSet author="fineract" id="1">
|
||||||
|
<insert tableName="m_external_event_configuration">
|
||||||
|
<column name="type" value="LoanTransactionInterestRefundPostBusinessEvent"/>
|
||||||
|
<column name="enabled" valueBoolean="false"/>
|
||||||
|
</insert>
|
||||||
|
<insert tableName="m_external_event_configuration">
|
||||||
|
<column name="type" value="LoanTransactionInterestRefundPreBusinessEvent"/>
|
||||||
|
<column name="enabled" valueBoolean="false"/>
|
||||||
|
</insert>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
+4
-2
@@ -104,7 +104,8 @@ public class ExternalEventConfigurationValidationServiceTest {
|
|||||||
"LoanUndoReAmortizeTransactionBusinessEvent", "LoanReAgeBusinessEvent", "LoanUndoReAgeBusinessEvent",
|
"LoanUndoReAmortizeTransactionBusinessEvent", "LoanReAgeBusinessEvent", "LoanUndoReAgeBusinessEvent",
|
||||||
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent",
|
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent",
|
||||||
"LoanTransactionInterestPaymentWaiverPostBusinessEvent", "LoanTransactionAccrualActivityPostBusinessEvent",
|
"LoanTransactionInterestPaymentWaiverPostBusinessEvent", "LoanTransactionAccrualActivityPostBusinessEvent",
|
||||||
"LoanTransactionAccrualActivityPreBusinessEvent");
|
"LoanTransactionAccrualActivityPreBusinessEvent", "LoanTransactionInterestRefundPostBusinessEvent",
|
||||||
|
"LoanTransactionInterestRefundPreBusinessEvent");
|
||||||
|
|
||||||
List<FineractPlatformTenant> tenants = Arrays
|
List<FineractPlatformTenant> tenants = Arrays
|
||||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||||
@@ -189,7 +190,8 @@ public class ExternalEventConfigurationValidationServiceTest {
|
|||||||
"LoanUndoReAmortizeTransactionBusinessEvent", "LoanReAgeBusinessEvent", "LoanUndoReAgeBusinessEvent",
|
"LoanUndoReAmortizeTransactionBusinessEvent", "LoanReAgeBusinessEvent", "LoanUndoReAgeBusinessEvent",
|
||||||
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent",
|
"LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent",
|
||||||
"LoanTransactionInterestPaymentWaiverPostBusinessEvent", "LoanTransactionAccrualActivityPostBusinessEvent",
|
"LoanTransactionInterestPaymentWaiverPostBusinessEvent", "LoanTransactionAccrualActivityPostBusinessEvent",
|
||||||
"LoanTransactionAccrualActivityPreBusinessEvent");
|
"LoanTransactionAccrualActivityPreBusinessEvent", "LoanTransactionInterestRefundPostBusinessEvent",
|
||||||
|
"LoanTransactionInterestRefundPreBusinessEvent");
|
||||||
|
|
||||||
List<FineractPlatformTenant> tenants = Arrays
|
List<FineractPlatformTenant> tenants = Arrays
|
||||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||||
|
|||||||
+12
-13
@@ -76,7 +76,6 @@ import org.apache.fineract.integrationtests.common.accounting.PeriodicAccrualAcc
|
|||||||
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
|
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||||
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.EarlyPaymentLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.EarlyPaymentLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor;
|
||||||
@@ -4426,7 +4425,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
.interestRatePerPeriod(5.0).interestCalculationPeriodType(DAYS).interestRateFrequencyType(YEARS)
|
.interestRatePerPeriod(5.0).interestCalculationPeriodType(DAYS).interestRateFrequencyType(YEARS)
|
||||||
.daysInMonthType(DaysInMonthType.DAYS_30.getValue()).daysInYearType(DaysInYearType.DAYS_360).numberOfRepayments(5)//
|
.daysInMonthType(DaysInMonthType.DAYS_30).daysInYearType(DaysInYearType.DAYS_360).numberOfRepayments(5)//
|
||||||
.repaymentEvery(1)//
|
.repaymentEvery(1)//
|
||||||
.repaymentFrequencyType(2L)//
|
.repaymentFrequencyType(2L)//
|
||||||
.enableDownPayment(true)//
|
.enableDownPayment(true)//
|
||||||
@@ -4488,7 +4487,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
|
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
|
||||||
.daysInMonthType(DaysInMonthType.DAYS_30.getValue()).daysInYearType(DaysInYearType.DAYS_360).numberOfRepayments(5)//
|
.daysInMonthType(DaysInMonthType.DAYS_30).daysInYearType(DaysInYearType.DAYS_360).numberOfRepayments(5)//
|
||||||
.repaymentEvery(1)//
|
.repaymentEvery(1)//
|
||||||
.repaymentFrequencyType(2L)//
|
.repaymentFrequencyType(2L)//
|
||||||
.enableDownPayment(true)//
|
.enableDownPayment(true)//
|
||||||
@@ -4551,7 +4550,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
|
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
|
||||||
.daysInMonthType(DaysInMonthType.DAYS_30.getValue()).daysInYearType(DaysInYearType.DAYS_360).numberOfRepayments(5)//
|
.daysInMonthType(DaysInMonthType.DAYS_30).daysInYearType(DaysInYearType.DAYS_360).numberOfRepayments(5)//
|
||||||
.repaymentEvery(1)//
|
.repaymentEvery(1)//
|
||||||
.repaymentFrequencyType(2L)//
|
.repaymentFrequencyType(2L)//
|
||||||
.enableDownPayment(true)//
|
.enableDownPayment(true)//
|
||||||
@@ -4614,7 +4613,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
|
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
|
||||||
.daysInMonthType(DaysInMonthType.ACTUAL.getValue()).daysInYearType(DaysInYearType.DAYS_365).numberOfRepayments(4)//
|
.daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.DAYS_365).numberOfRepayments(4)//
|
||||||
.repaymentEvery(1)//
|
.repaymentEvery(1)//
|
||||||
.repaymentFrequencyType(2L)//
|
.repaymentFrequencyType(2L)//
|
||||||
.allowPartialPeriodInterestCalcualtion(false)//
|
.allowPartialPeriodInterestCalcualtion(false)//
|
||||||
@@ -4692,8 +4691,8 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
||||||
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL.getValue())
|
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.DAYS_360)
|
||||||
.daysInYearType(DaysInYearType.DAYS_360).numberOfRepayments(4)//
|
.numberOfRepayments(4)//
|
||||||
.maxInterestRatePerPeriod((double) 110)//
|
.maxInterestRatePerPeriod((double) 110)//
|
||||||
.repaymentEvery(1)//
|
.repaymentEvery(1)//
|
||||||
.repaymentFrequencyType(1L)//
|
.repaymentFrequencyType(1L)//
|
||||||
@@ -4873,7 +4872,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
interestRefundTypes.add("MERCHANT_ISSUED_REFUND");
|
interestRefundTypes.add("MERCHANT_ISSUED_REFUND");
|
||||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
.interestRatePerPeriod(12.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
|
.interestRatePerPeriod(12.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
|
||||||
.daysInMonthType(DaysInMonthType.ACTUAL.getValue()).daysInYearType(DaysInYearType.DAYS_365).numberOfRepayments(4)//
|
.daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.DAYS_365).numberOfRepayments(4)//
|
||||||
.repaymentEvery(5)//
|
.repaymentEvery(5)//
|
||||||
.repaymentFrequencyType(0L)//
|
.repaymentFrequencyType(0L)//
|
||||||
.allowPartialPeriodInterestCalcualtion(false)//
|
.allowPartialPeriodInterestCalcualtion(false)//
|
||||||
@@ -4935,8 +4934,8 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
.interestRatePerPeriod(interestRatePerPeriod.doubleValue()).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
.interestRatePerPeriod(interestRatePerPeriod.doubleValue()).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
||||||
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.DAYS_30.getValue())
|
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.DAYS_30).daysInYearType(DaysInYearType.DAYS_360)
|
||||||
.daysInYearType(DaysInYearType.DAYS_360).numberOfRepayments(6)//
|
.numberOfRepayments(6)//
|
||||||
.repaymentEvery(1)//
|
.repaymentEvery(1)//
|
||||||
.repaymentFrequencyType(2L)//
|
.repaymentFrequencyType(2L)//
|
||||||
.enableDownPayment(false)//
|
.enableDownPayment(false)//
|
||||||
@@ -5017,8 +5016,8 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
.interestRatePerPeriod(interestRatePerPeriod.doubleValue()).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
.interestRatePerPeriod(interestRatePerPeriod.doubleValue()).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
||||||
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.DAYS_30.getValue())
|
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.DAYS_30).daysInYearType(DaysInYearType.DAYS_360)
|
||||||
.daysInYearType(DaysInYearType.DAYS_360).numberOfRepayments(6)//
|
.numberOfRepayments(6)//
|
||||||
.repaymentEvery(1)//
|
.repaymentEvery(1)//
|
||||||
.repaymentFrequencyType(2L)//
|
.repaymentFrequencyType(2L)//
|
||||||
.enableDownPayment(false)//
|
.enableDownPayment(false)//
|
||||||
@@ -5114,7 +5113,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
.interestRatePerPeriod(interestRatePerPeriod.doubleValue())//
|
.interestRatePerPeriod(interestRatePerPeriod.doubleValue())//
|
||||||
.interestCalculationPeriodType(RepaymentFrequencyType.DAYS)//
|
.interestCalculationPeriodType(RepaymentFrequencyType.DAYS)//
|
||||||
.interestRateFrequencyType(YEARS)//
|
.interestRateFrequencyType(YEARS)//
|
||||||
.daysInMonthType(DaysInMonthType.DAYS_30.getValue())//
|
.daysInMonthType(DaysInMonthType.DAYS_30)//
|
||||||
.daysInYearType(DaysInYearType.DAYS_360)//
|
.daysInYearType(DaysInYearType.DAYS_360)//
|
||||||
.numberOfRepayments(6)//
|
.numberOfRepayments(6)//
|
||||||
.repaymentEvery(1)//
|
.repaymentEvery(1)//
|
||||||
|
|||||||
+7
@@ -1281,6 +1281,13 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
public static final Integer DAYS_365 = 365;
|
public static final Integer DAYS_365 = 365;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DaysInMonthType {
|
||||||
|
|
||||||
|
public static final Integer INVALID = 0;
|
||||||
|
public static final Integer ACTUAL = 1;
|
||||||
|
public static final Integer DAYS_30 = 30;
|
||||||
|
}
|
||||||
|
|
||||||
public static class FuturePaymentAllocationRule {
|
public static class FuturePaymentAllocationRule {
|
||||||
|
|
||||||
public static final String LAST_INSTALLMENT = "LAST_INSTALLMENT";
|
public static final String LAST_INSTALLMENT = "LAST_INSTALLMENT";
|
||||||
|
|||||||
+164
-50
@@ -25,9 +25,11 @@ import io.restassured.builder.ResponseSpecBuilder;
|
|||||||
import io.restassured.http.ContentType;
|
import io.restassured.http.ContentType;
|
||||||
import io.restassured.specification.RequestSpecification;
|
import io.restassured.specification.RequestSpecification;
|
||||||
import io.restassured.specification.ResponseSpecification;
|
import io.restassured.specification.ResponseSpecification;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -35,6 +37,8 @@ import java.util.Objects;
|
|||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -42,6 +46,8 @@ import org.apache.fineract.client.models.PostClientsResponse;
|
|||||||
import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
|
import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
|
||||||
import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse;
|
import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse;
|
||||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||||
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansRequest;
|
import org.apache.fineract.client.models.PostLoansRequest;
|
||||||
import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
|
import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
|
||||||
import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
|
import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO;
|
||||||
@@ -111,7 +117,7 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
loanTransactionHelper.makeLoanRepayment("15 March 2023", 125.0F, loanIdRef.get().intValue());
|
loanTransactionHelper.makeLoanRepayment("15 March 2023", 125.0F, loanIdRef.get().intValue());
|
||||||
|
|
||||||
verifyBusinessEvents(new BusinessEvent("LoanBalanceChangedBusinessEvent", "15 March 2023", 300, 400.0, 291.04));
|
verifyBusinessEvents(new LoanBusinessEvent("LoanBalanceChangedBusinessEvent", "15 March 2023", 300, 400.0, 291.04));
|
||||||
});
|
});
|
||||||
runAt("1 April 2023", () -> {
|
runAt("1 April 2023", () -> {
|
||||||
|
|
||||||
@@ -123,7 +129,7 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
loanTransactionHelper.makeLoanRepayment("15 April 2023", 125.0F, loanIdRef.get().intValue());
|
loanTransactionHelper.makeLoanRepayment("15 April 2023", 125.0F, loanIdRef.get().intValue());
|
||||||
|
|
||||||
verifyBusinessEvents(new BusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 300, 1000.0, 770.06));
|
verifyBusinessEvents(new LoanBusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 300, 1000.0, 770.06));
|
||||||
|
|
||||||
deleteAllExternalEvents();
|
deleteAllExternalEvents();
|
||||||
|
|
||||||
@@ -131,24 +137,57 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
|
|||||||
.getResourceId();
|
.getResourceId();
|
||||||
Assertions.assertNotNull(transactionId);
|
Assertions.assertNotNull(transactionId);
|
||||||
|
|
||||||
verifyBusinessEvents(new BusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 700, 1000.0, 0.0));
|
verifyBusinessEvents(new LoanBusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 700, 1000.0, 0.0));
|
||||||
|
|
||||||
deleteAllExternalEvents();
|
deleteAllExternalEvents();
|
||||||
|
|
||||||
loanTransactionHelper.reverseRepayment(loanIdRef.get().intValue(), transactionId.intValue(), "15 April 2023");
|
loanTransactionHelper.reverseRepayment(loanIdRef.get().intValue(), transactionId.intValue(), "15 April 2023");
|
||||||
|
|
||||||
verifyBusinessEvents(new BusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 300, 1000.0, 770.06));
|
verifyBusinessEvents(new LoanBusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 300, 1000.0, 770.06));
|
||||||
|
|
||||||
deleteAllExternalEvents();
|
deleteAllExternalEvents();
|
||||||
|
|
||||||
loanTransactionHelper.makeLoanRepayment("15 April 2023", 830.22F, loanIdRef.get().intValue());
|
loanTransactionHelper.makeLoanRepayment("15 April 2023", 830.22F, loanIdRef.get().intValue());
|
||||||
|
|
||||||
verifyBusinessEvents(new BusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 600, 1000.0, 0.0));
|
verifyBusinessEvents(new LoanBusinessEvent("LoanBalanceChangedBusinessEvent", "15 April 2023", 600, 1000.0, 0.0));
|
||||||
|
|
||||||
disableLoanBalanceChangedBusinessEvent();
|
disableLoanBalanceChangedBusinessEvent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyInterestRefundPostBusinessEventCreatedForMerchantIssuedRefundWithInterestRefund() {
|
||||||
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
enableLoanInterestRefundPstBusinessEvent(true);
|
||||||
|
runAt("1 January 2021", () -> {
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||||
|
.supportedInterestRefundTypes(new ArrayList<>()).addSupportedInterestRefundTypesItem("MERCHANT_ISSUED_REFUND") //
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||||
|
);
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.99,
|
||||||
|
12, null);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
|
||||||
|
});
|
||||||
|
runAt("22 January 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
|
||||||
|
deleteAllExternalEvents();
|
||||||
|
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||||
|
.makeLoanRepayment("MerchantIssuedRefund", "22 January 2021", 1000F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
verifyBusinessEvents(new LoanTransactionBusinessEvent("LoanTransactionInterestRefundPostBusinessEvent", "22 January 2021", 5.75,
|
||||||
|
994.25, 5.75, 0.0, 0.0, 0.0));
|
||||||
|
});
|
||||||
|
enableLoanInterestRefundPstBusinessEvent(false);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExternalBusinessEventLoanRescheduledDueAdjustScheduleBusinessEventInterestChange() {
|
public void testExternalBusinessEventLoanRescheduledDueAdjustScheduleBusinessEventInterestChange() {
|
||||||
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
@@ -171,7 +210,7 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
|
|||||||
loanRescheduleRequestHelper.approveLoanRescheduleRequest(rescheduleLoansResponse.getResourceId(),
|
loanRescheduleRequestHelper.approveLoanRescheduleRequest(rescheduleLoansResponse.getResourceId(),
|
||||||
new PostUpdateRescheduleLoansRequest().approvedOnDate("1 March 2024").locale("en").dateFormat(DATETIME_PATTERN));
|
new PostUpdateRescheduleLoansRequest().approvedOnDate("1 March 2024").locale("en").dateFormat(DATETIME_PATTERN));
|
||||||
|
|
||||||
verifyBusinessEvents(new BusinessEvent("LoanRescheduledDueAdjustScheduleBusinessEvent", "01 March 2024", 300, 400.0, 400.0,
|
verifyBusinessEvents(new LoanBusinessEvent("LoanRescheduledDueAdjustScheduleBusinessEvent", "01 March 2024", 300, 400.0, 400.0,
|
||||||
List.of("interestRateForInstallment")));
|
List.of("interestRateForInstallment")));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -260,11 +299,28 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
private void logBusinessEvents(List<ExternalEventDTO> allExternalEvents) {
|
private void logBusinessEvents(List<ExternalEventDTO> allExternalEvents) {
|
||||||
allExternalEvents.forEach(externalEventDTO -> {
|
allExternalEvents.forEach(externalEventDTO -> {
|
||||||
log.info("Event Received { type:'{}' businessDate:'{}' }", externalEventDTO.getType(), externalEventDTO.getBusinessDate());
|
Object amount = externalEventDTO.getPayLoad().get("amount");
|
||||||
log.debug(externalEventDTO.toString());
|
Object outstandingLoanBalance = externalEventDTO.getPayLoad().get("outstandingLoanBalance");
|
||||||
|
Object principalPortion = externalEventDTO.getPayLoad().get("principalPortion");
|
||||||
|
Object interestPortion = externalEventDTO.getPayLoad().get("interestPortion");
|
||||||
|
Object feePortion = externalEventDTO.getPayLoad().get("feeChargesPortion");
|
||||||
|
Object penaltyPortion = externalEventDTO.getPayLoad().get("penaltyChargesPortion");
|
||||||
|
log.info("Event Received\n type:'{}'\n businessDate:'{}'", externalEventDTO.getType(), externalEventDTO.getBusinessDate());
|
||||||
|
log.info(
|
||||||
|
"Values\n amount: {}\n outstandingLoanBalance: {}\n principalPortion: {}\n interestPortion: {}\n feePortion: {}\n penaltyPortion: {}",
|
||||||
|
amount, outstandingLoanBalance, principalPortion, interestPortion, feePortion, penaltyPortion);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void enableLoanInterestRefundPstBusinessEvent(boolean enabled) {
|
||||||
|
final Map<String, Boolean> updatedConfigurations = ExternalEventConfigurationHelper.updateExternalEventConfigurations(requestSpec,
|
||||||
|
responseSpec, "{\"externalEventConfigurations\":{\"LoanTransactionInterestRefundPostBusinessEvent\":"
|
||||||
|
+ (enabled ? "true" : "false") + "}}\n");
|
||||||
|
Assertions.assertEquals(updatedConfigurations.size(), 1);
|
||||||
|
Assertions.assertTrue(updatedConfigurations.containsKey("LoanTransactionInterestRefundPostBusinessEvent"));
|
||||||
|
Assertions.assertEquals(enabled, updatedConfigurations.get("LoanTransactionInterestRefundPostBusinessEvent"));
|
||||||
|
}
|
||||||
|
|
||||||
public void verifyBusinessEvents(BusinessEvent... businessEvents) {
|
public void verifyBusinessEvents(BusinessEvent... businessEvents) {
|
||||||
List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
|
List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec);
|
||||||
logBusinessEvents(allExternalEvents);
|
logBusinessEvents(allExternalEvents);
|
||||||
@@ -273,61 +329,119 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
|
|||||||
Assertions.assertTrue(businessEvents.length <= allExternalEvents.size(),
|
Assertions.assertTrue(businessEvents.length <= allExternalEvents.size(),
|
||||||
"Expected business event count is less than actual. Expected: " + businessEvents.length + " Actual: "
|
"Expected business event count is less than actual. Expected: " + businessEvents.length + " Actual: "
|
||||||
+ allExternalEvents.size());
|
+ allExternalEvents.size());
|
||||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN, Locale.ENGLISH);
|
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN, Locale.ENGLISH);
|
||||||
for (BusinessEvent businessEvent : businessEvents) {
|
for (BusinessEvent businessEvent : businessEvents) {
|
||||||
long count = allExternalEvents.stream().filter(externalEvent -> {
|
long count = allExternalEvents.stream().filter(externalEvent -> businessEvent.verify(externalEvent, formatter)).count();
|
||||||
Object summaryRes = externalEvent.getPayLoad().get("summary");
|
|
||||||
Object statusRes = externalEvent.getPayLoad().get("status");
|
|
||||||
Map<String, Object> summary = summaryRes instanceof Map ? (Map<String, Object>) summaryRes : Map.of();
|
|
||||||
Map<String, Object> status = statusRes instanceof Map ? (Map<String, Object>) statusRes : Map.of();
|
|
||||||
var principalDisbursed = summary.get("principalDisbursed");
|
|
||||||
|
|
||||||
var principalOutstanding = summary.get("principalOutstanding");
|
|
||||||
var businessDate = LocalDate.parse(businessEvent.getBusinessDate(), formatter);
|
|
||||||
Double statusId = (Double) status.get("id");
|
|
||||||
return Objects.equals(externalEvent.getType(), businessEvent.getType())
|
|
||||||
&& Objects.equals(externalEvent.getBusinessDate(), businessDate)
|
|
||||||
&& Objects.equals(statusId, businessEvent.getStatusId().doubleValue())
|
|
||||||
&& Objects.equals(principalDisbursed, businessEvent.getPrincipalDisbursed())
|
|
||||||
&& Objects.equals(principalOutstanding, businessEvent.getPrincipalOutstanding())
|
|
||||||
&& loanTermVariationsMatch((List<Map<String, Object>>) externalEvent.getPayLoad().get("loanTermVariations"),
|
|
||||||
businessEvent.loanTermVariationType);
|
|
||||||
}).count();
|
|
||||||
Assertions.assertEquals(1, count, "Expected business event not found " + businessEvent);
|
Assertions.assertEquals(1, count, "Expected business event not found " + businessEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean loanTermVariationsMatch(final List<Map<String, Object>> loanTermVariations, final List<String> expectedTypes) {
|
|
||||||
if (CollectionUtils.isEmpty(expectedTypes)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
final long numberOfMatches = expectedTypes
|
|
||||||
.stream().filter(
|
|
||||||
expectedType -> loanTermVariations.stream()
|
|
||||||
.anyMatch(variation -> StringUtils
|
|
||||||
.equals((String) ((Map<String, Object>) variation.get("termType")).get("value"), expectedType)))
|
|
||||||
.count();
|
|
||||||
|
|
||||||
return numberOfMatches == expectedTypes.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
public static class BusinessEvent {
|
public static class BusinessEvent {
|
||||||
|
|
||||||
public BusinessEvent(String type, String businessDate, Integer statusId, Double principalDisbursed, Double principalOutstanding) {
|
String type;
|
||||||
this.type = type;
|
String businessDate;
|
||||||
this.businessDate = businessDate;
|
|
||||||
|
boolean verify(@NotNull ExternalEventDTO externalEvent, DateTimeFormatter formatter) {
|
||||||
|
var businessDate = LocalDate.parse(getBusinessDate(), formatter);
|
||||||
|
|
||||||
|
return Objects.equals(externalEvent.getType(), getType()) && Objects.equals(externalEvent.getBusinessDate(), businessDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class LoanTransactionBusinessEvent extends BusinessEvent {
|
||||||
|
|
||||||
|
private Double amount;
|
||||||
|
private Double outstandingLoanBalance;
|
||||||
|
private Double principalPortion;
|
||||||
|
private Double interestPortion;
|
||||||
|
private Double feeChargesPortion;
|
||||||
|
private Double penaltyChargesPortion;
|
||||||
|
|
||||||
|
public LoanTransactionBusinessEvent(String type, String businessDate, Double amount, Double outstandingLoanBalance,
|
||||||
|
Double principalPortion, Double interestPortion, Double feeChargesPortion, Double penaltyChargesPortion) {
|
||||||
|
super(type, businessDate);
|
||||||
|
this.amount = amount;
|
||||||
|
this.outstandingLoanBalance = outstandingLoanBalance;
|
||||||
|
this.principalPortion = principalPortion;
|
||||||
|
this.interestPortion = interestPortion;
|
||||||
|
this.feeChargesPortion = feeChargesPortion;
|
||||||
|
this.penaltyChargesPortion = penaltyChargesPortion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean verify(ExternalEventDTO externalEvent, DateTimeFormatter formatter) {
|
||||||
|
Object amount = externalEvent.getPayLoad().get("amount");
|
||||||
|
Object outstandingLoanBalance = externalEvent.getPayLoad().get("outstandingLoanBalance");
|
||||||
|
Object principalPortion = externalEvent.getPayLoad().get("principalPortion");
|
||||||
|
Object interestPortion = externalEvent.getPayLoad().get("interestPortion");
|
||||||
|
Object feePortion = externalEvent.getPayLoad().get("feeChargesPortion");
|
||||||
|
Object penaltyPortion = externalEvent.getPayLoad().get("penaltyChargesPortion");
|
||||||
|
|
||||||
|
return super.verify(externalEvent, formatter) && Objects.equals(amount, getAmount())
|
||||||
|
&& Objects.equals(outstandingLoanBalance, getOutstandingLoanBalance())
|
||||||
|
&& Objects.equals(principalPortion, getPrincipalPortion()) && Objects.equals(interestPortion, getInterestPortion())
|
||||||
|
&& Objects.equals(feePortion, getFeeChargesPortion()) && Objects.equals(penaltyPortion, getPenaltyChargesPortion());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class LoanBusinessEvent extends BusinessEvent {
|
||||||
|
|
||||||
|
private Integer statusId;
|
||||||
|
private Double principalDisbursed;
|
||||||
|
private Double principalOutstanding;
|
||||||
|
private List<String> loanTermVariationType;
|
||||||
|
|
||||||
|
public LoanBusinessEvent(String type, String businessDate, Integer statusId, Double principalDisbursed,
|
||||||
|
Double principalOutstanding) {
|
||||||
|
super(type, businessDate);
|
||||||
this.statusId = statusId;
|
this.statusId = statusId;
|
||||||
this.principalDisbursed = principalDisbursed;
|
this.principalDisbursed = principalDisbursed;
|
||||||
this.principalOutstanding = principalOutstanding;
|
this.principalOutstanding = principalOutstanding;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String type;
|
public LoanBusinessEvent(String type, String businessDate, Integer statusId, Double principalDisbursed, Double principalOutstanding,
|
||||||
private String businessDate;
|
List<String> loanTermVariationType) {
|
||||||
private Integer statusId;
|
super(type, businessDate);
|
||||||
private Double principalDisbursed;
|
this.statusId = statusId;
|
||||||
private Double principalOutstanding;
|
this.principalDisbursed = principalDisbursed;
|
||||||
private List<String> loanTermVariationType;
|
this.principalOutstanding = principalOutstanding;
|
||||||
|
this.loanTermVariationType = loanTermVariationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verify(ExternalEventDTO externalEvent, DateTimeFormatter formatter) {
|
||||||
|
Object summaryRes = externalEvent.getPayLoad().get("summary");
|
||||||
|
Object statusRes = externalEvent.getPayLoad().get("status");
|
||||||
|
Map<String, Object> summary = summaryRes instanceof Map ? (Map<String, Object>) summaryRes : Map.of();
|
||||||
|
Map<String, Object> status = statusRes instanceof Map ? (Map<String, Object>) statusRes : Map.of();
|
||||||
|
var principalDisbursed = summary.get("principalDisbursed");
|
||||||
|
|
||||||
|
var principalOutstanding = summary.get("principalOutstanding");
|
||||||
|
Double statusId = (Double) status.get("id");
|
||||||
|
return super.verify(externalEvent, formatter) && Objects.equals(statusId, getStatusId().doubleValue())
|
||||||
|
&& Objects.equals(principalDisbursed, getPrincipalDisbursed())
|
||||||
|
&& Objects.equals(principalOutstanding, getPrincipalOutstanding()) && loanTermVariationsMatch(
|
||||||
|
(List<Map<String, Object>>) externalEvent.getPayLoad().get("loanTermVariations"), loanTermVariationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean loanTermVariationsMatch(final List<Map<String, Object>> loanTermVariations, final List<String> expectedTypes) {
|
||||||
|
if (CollectionUtils.isEmpty(expectedTypes)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final long numberOfMatches = expectedTypes.stream().filter(expectedType -> loanTermVariations.stream().anyMatch(
|
||||||
|
variation -> StringUtils.equals((String) ((Map<String, Object>) variation.get("termType")).get("value"), expectedType)))
|
||||||
|
.count();
|
||||||
|
|
||||||
|
return numberOfMatches == expectedTypes.size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+412
@@ -0,0 +1,412 @@
|
|||||||
|
/**
|
||||||
|
* 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.integrationtests;
|
||||||
|
|
||||||
|
import io.restassured.builder.RequestSpecBuilder;
|
||||||
|
import io.restassured.builder.ResponseSpecBuilder;
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
import io.restassured.specification.RequestSpecification;
|
||||||
|
import io.restassured.specification.ResponseSpecification;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
|
import org.apache.fineract.client.models.PostClientsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
||||||
|
import org.apache.fineract.integrationtests.common.BusinessStepHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
||||||
|
|
||||||
|
private static ResponseSpecification responseSpec;
|
||||||
|
private static RequestSpecification requestSpec;
|
||||||
|
private static LoanTransactionHelper loanTransactionHelper;
|
||||||
|
private static PostClientsResponse client;
|
||||||
|
private static BusinessStepHelper businessStepHelper;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setup() {
|
||||||
|
Utils.initializeRESTAssured();
|
||||||
|
requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||||
|
requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||||
|
requestSpec.header("Fineract-Platform-TenantId", "default");
|
||||||
|
responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||||
|
loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
|
||||||
|
ClientHelper clientHelper = new ClientHelper(requestSpec, responseSpec);
|
||||||
|
client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyInterestRefundNotCreatedForPayoutRefundWhenTypesAreEmpty() {
|
||||||
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
runAt("1 January 2021", () -> {
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||||
|
.supportedInterestRefundTypes(new ArrayList<>()) //
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||||
|
);
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.9,
|
||||||
|
12, null);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
|
||||||
|
});
|
||||||
|
runAt("22 January 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||||
|
.makeLoanRepayment("PayoutRefund", "22 January 2021", 1000F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||||
|
transaction(1000.0, "Payout Refund", "22 January 2021"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyInterestRefundNotCreatedForMerchantIssuedRefundWhenTypesAreEmpty() {
|
||||||
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
runAt("1 January 2021", () -> {
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||||
|
.supportedInterestRefundTypes(new ArrayList<>())//
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||||
|
);
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.9,
|
||||||
|
12, null);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
|
||||||
|
});
|
||||||
|
runAt("22 January 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||||
|
.makeLoanRepayment("MerchantIssuedRefund", "22 January 2021", 1000F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||||
|
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyInterestRefundCreatedForPayoutRefund() {
|
||||||
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
runAt("1 January 2021", () -> {
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||||
|
.supportedInterestRefundTypes(new ArrayList<>()).addSupportedInterestRefundTypesItem("PAYOUT_REFUND") //
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||||
|
);
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.99,
|
||||||
|
12, null);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
|
||||||
|
});
|
||||||
|
runAt("22 January 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||||
|
.makeLoanRepayment("PayoutRefund", "22 January 2021", 1000F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||||
|
transaction(1000.0, "Payout Refund", "22 January 2021"), transaction(5.75, "Accrual", "22 January 2021"),
|
||||||
|
transaction(5.75, "Interest Refund", "22 January 2021"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyInterestRefundCreatedForMerchantIssuedRefund() {
|
||||||
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
runAt("1 January 2021", () -> {
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||||
|
.supportedInterestRefundTypes(new ArrayList<>()).addSupportedInterestRefundTypesItem("MERCHANT_ISSUED_REFUND") //
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||||
|
);
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.99,
|
||||||
|
12, null);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
|
||||||
|
});
|
||||||
|
runAt("22 January 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||||
|
.makeLoanRepayment("MerchantIssuedRefund", "22 January 2021", 1000F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||||
|
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), transaction(5.75, "Accrual", "22 January 2021"),
|
||||||
|
transaction(5.75, "Interest Refund", "22 January 2021"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyUC01() {
|
||||||
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
runAt("1 January 2021", () -> {
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||||
|
.supportedInterestRefundTypes(new ArrayList<>()).addSupportedInterestRefundTypesItem("PAYOUT_REFUND") //
|
||||||
|
.addSupportedInterestRefundTypesItem("MERCHANT_ISSUED_REFUND") //
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||||
|
);
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.99,
|
||||||
|
12, null);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
|
||||||
|
});
|
||||||
|
runAt("22 January 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||||
|
.makeLoanRepayment("PayoutRefund", "22 January 2021", 1000F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||||
|
transaction(1000.0, "Payout Refund", "22 January 2021"), transaction(5.75, "Accrual", "22 January 2021"),
|
||||||
|
transaction(5.75, "Interest Refund", "22 January 2021"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyUC02a() {
|
||||||
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
runAt("1 January 2021", () -> {
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||||
|
.supportedInterestRefundTypes(new ArrayList<>()).addSupportedInterestRefundTypesItem("PAYOUT_REFUND") //
|
||||||
|
.addSupportedInterestRefundTypesItem("MERCHANT_ISSUED_REFUND") //
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||||
|
);
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.99,
|
||||||
|
12, null);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
|
||||||
|
});
|
||||||
|
runAt("1 February 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||||
|
.makeLoanRepayment("PayoutRefund", "1 February 2021", 1000F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||||
|
transaction(1000.0, "Payout Refund", "01 February 2021"), transaction(8.48, "Accrual", "01 February 2021"),
|
||||||
|
transaction(8.48, "Interest Refund", "01 February 2021"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyUC02b() {
|
||||||
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
runAt("1 January 2021", () -> {
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||||
|
.supportedInterestRefundTypes(new ArrayList<>()).addSupportedInterestRefundTypesItem("PAYOUT_REFUND") //
|
||||||
|
.addSupportedInterestRefundTypesItem("MERCHANT_ISSUED_REFUND") //
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||||
|
);
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.99,
|
||||||
|
12, null);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
|
||||||
|
});
|
||||||
|
runAt("1 February 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment("Repayment",
|
||||||
|
"1 February 2021", 87.89F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||||
|
transaction(87.89, "Repayment", "01 February 2021"));
|
||||||
|
});
|
||||||
|
|
||||||
|
runAt("9 February 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||||
|
.makeLoanRepayment("PayoutRefund", "9 February 2021", 1000F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
||||||
|
transaction(1000.0, "Payout Refund", "09 February 2021"), transaction(87.89, "Repayment", "01 February 2021"),
|
||||||
|
transaction(10.50, "Interest Refund", "09 February 2021"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyUC03() {
|
||||||
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
runAt("1 January 2021", () -> {
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||||
|
.supportedInterestRefundTypes(new ArrayList<>())//
|
||||||
|
.disallowExpectedDisbursements(true)//
|
||||||
|
.multiDisburseLoan(true)//
|
||||||
|
.maxTrancheCount(2).addSupportedInterestRefundTypesItem("PAYOUT_REFUND") //
|
||||||
|
.addSupportedInterestRefundTypesItem("MERCHANT_ISSUED_REFUND") //
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||||
|
);
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.99,
|
||||||
|
12, null);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(750), "1 January 2021");
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(250), "1 January 2021");
|
||||||
|
});
|
||||||
|
runAt("22 January 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||||
|
.makeLoanRepayment("PayoutRefund", "22 January 2021", 1000F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(750.0, "Disbursement", "01 January 2021"),
|
||||||
|
transaction(250.0, "Disbursement", "01 January 2021"), transaction(1000.0, "Payout Refund", "22 January 2021"),
|
||||||
|
transaction(5.75, "Accrual", "22 January 2021"), transaction(5.75, "Interest Refund", "22 January 2021"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyUC04() {
|
||||||
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
runAt("1 January 2021", () -> {
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||||
|
.supportedInterestRefundTypes(new ArrayList<>()).disallowExpectedDisbursements(true).multiDisburseLoan(true)
|
||||||
|
.maxTrancheCount(2).addSupportedInterestRefundTypesItem("PAYOUT_REFUND") //
|
||||||
|
.addSupportedInterestRefundTypesItem("MERCHANT_ISSUED_REFUND") //
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||||
|
);
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.99,
|
||||||
|
12, null);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(250), "1 January 2021");
|
||||||
|
});
|
||||||
|
runAt("4 January 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(750), "4 January 2021");
|
||||||
|
});
|
||||||
|
runAt("22 January 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||||
|
.makeLoanRepayment("PayoutRefund", "22 January 2021", 1000F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(750.0, "Disbursement", "04 January 2021"),
|
||||||
|
transaction(250.0, "Disbursement", "01 January 2021"), transaction(1000.0, "Payout Refund", "22 January 2021"),
|
||||||
|
transaction(5.14, "Accrual", "22 January 2021"), transaction(5.14, "Interest Refund", "22 January 2021"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyUC05() {
|
||||||
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
runAt("1 January 2021", () -> {
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||||
|
.supportedInterestRefundTypes(new ArrayList<>()).disallowExpectedDisbursements(true).multiDisburseLoan(true)
|
||||||
|
.maxTrancheCount(2).addSupportedInterestRefundTypesItem("PAYOUT_REFUND") //
|
||||||
|
.addSupportedInterestRefundTypesItem("MERCHANT_ISSUED_REFUND") //
|
||||||
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||||
|
);
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.99,
|
||||||
|
12, null);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(500), "1 January 2021");
|
||||||
|
});
|
||||||
|
runAt("7 January 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(500), "7 January 2021");
|
||||||
|
});
|
||||||
|
runAt("1 February 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment("Repayment",
|
||||||
|
"1 February 2021", 87.82F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(500.0, "Disbursement", "01 January 2021"),
|
||||||
|
transaction(500.0, "Disbursement", "07 January 2021"), transaction(87.82, "Repayment", "01 February 2021"));
|
||||||
|
});
|
||||||
|
|
||||||
|
runAt("9 February 2021", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||||
|
.makeLoanRepayment("PayoutRefund", "9 February 2021", 1000F, loanId.intValue());
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||||
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
|
logTransactions(loanId);
|
||||||
|
verifyTransactions(loanId, transaction(500.0, "Disbursement", "01 January 2021"),
|
||||||
|
transaction(500.0, "Disbursement", "07 January 2021"), transaction(1000.0, "Payout Refund", "09 February 2021"),
|
||||||
|
transaction(87.82, "Repayment", "01 February 2021"), transaction(9.67, "Interest Refund", "09 February 2021"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logTransactions(Long loanId) {
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
|
||||||
|
assert loanDetails.getTransactions() != null;
|
||||||
|
loanDetails.getTransactions()
|
||||||
|
.forEach(tr -> log.info("Transaction {} {} {} ", tr.getType().getValue(), tr.getDate(), tr.getAmount()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+4
-5
@@ -44,7 +44,6 @@ import org.apache.fineract.integrationtests.common.Utils;
|
|||||||
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
|
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||||
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -89,8 +88,8 @@ public class LoanRefundTransactionTest extends BaseLoanIntegrationTest {
|
|||||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
||||||
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL.getValue())
|
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.DAYS_360)
|
||||||
.daysInYearType(DaysInYearType.DAYS_360).numberOfRepayments(4)//
|
.numberOfRepayments(4)//
|
||||||
.maxInterestRatePerPeriod((double) 110)//
|
.maxInterestRatePerPeriod((double) 110)//
|
||||||
.repaymentEvery(1)//
|
.repaymentEvery(1)//
|
||||||
.repaymentFrequencyType(1L)//
|
.repaymentFrequencyType(1L)//
|
||||||
@@ -161,8 +160,8 @@ public class LoanRefundTransactionTest extends BaseLoanIntegrationTest {
|
|||||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
||||||
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL.getValue())
|
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.DAYS_360)
|
||||||
.daysInYearType(DaysInYearType.DAYS_360).numberOfRepayments(4)//
|
.numberOfRepayments(4)//
|
||||||
.maxInterestRatePerPeriod((double) 110)//
|
.maxInterestRatePerPeriod((double) 110)//
|
||||||
.repaymentEvery(1)//
|
.repaymentEvery(1)//
|
||||||
.repaymentFrequencyType(1L)//
|
.repaymentFrequencyType(1L)//
|
||||||
|
|||||||
+10
@@ -560,6 +560,16 @@ public class ExternalEventConfigurationHelper {
|
|||||||
loanTransactionAccrualActivityPreBusinessEvent.put("enabled", false);
|
loanTransactionAccrualActivityPreBusinessEvent.put("enabled", false);
|
||||||
defaults.add(loanTransactionAccrualActivityPreBusinessEvent);
|
defaults.add(loanTransactionAccrualActivityPreBusinessEvent);
|
||||||
|
|
||||||
|
Map<String, Object> loanTransactionInterestRefundPostBusinessEvent = new HashMap<>();
|
||||||
|
loanTransactionInterestRefundPostBusinessEvent.put("type", "LoanTransactionInterestRefundPostBusinessEvent");
|
||||||
|
loanTransactionInterestRefundPostBusinessEvent.put("enabled", false);
|
||||||
|
defaults.add(loanTransactionInterestRefundPostBusinessEvent);
|
||||||
|
|
||||||
|
Map<String, Object> loanTransactionInterestRefundPreBusinessEvent = new HashMap<>();
|
||||||
|
loanTransactionInterestRefundPreBusinessEvent.put("type", "LoanTransactionInterestRefundPreBusinessEvent");
|
||||||
|
loanTransactionInterestRefundPreBusinessEvent.put("enabled", false);
|
||||||
|
defaults.add(loanTransactionInterestRefundPreBusinessEvent);
|
||||||
|
|
||||||
return defaults;
|
return defaults;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user