FINERACT-2107: Interest Refund - Allocation, Business Event & Full Repayment

This commit is contained in:
Soma Sörös
2024-10-21 17:36:20 +02:00
committed by Adam Saghy
parent 84d606552d
commit 5722891477
18 changed files with 969 additions and 77 deletions
@@ -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;
}
}
@@ -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;
}
}
@@ -1718,6 +1718,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
public List<Long> findExistingTransactionIds() {
return getLoanTransactions().stream() //
.filter(loanTransaction -> loanTransaction.getId() != null) //
.map(LoanTransaction::getId) //
.collect(Collectors.toList());
}
@@ -1725,6 +1726,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
public List<Long> findExistingReversedTransactionIds() {
return getLoanTransactions().stream() //
.filter(LoanTransaction::isReversed) //
.filter(loanTransaction -> loanTransaction.getId() != null) //
.map(LoanTransaction::getId) //
.collect(Collectors.toList());
}
@@ -2256,7 +2258,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
private void validateRepaymentTypeAccountStatus(LoanTransaction repaymentTransaction, LoanEvent event) {
if (repaymentTransaction.isGoodwillCredit() || repaymentTransaction.isInterestPaymentWaiver()
|| repaymentTransaction.isMerchantIssuedRefund() || repaymentTransaction.isPayoutRefund()
|| repaymentTransaction.isChargeRefund() || repaymentTransaction.isRepayment() || repaymentTransaction.isDownPayment()) {
|| repaymentTransaction.isChargeRefund() || repaymentTransaction.isRepayment() || repaymentTransaction.isDownPayment()
|| repaymentTransaction.isInterestRefund()) {
if (!(isOpen() || isClosedObligationsMet() || isOverPaid())) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
@@ -189,6 +189,11 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
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,
final ExternalId externalId, PaymentDetail paymentDetail) {
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() {
return isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isChargeRefund()
|| isChargeAdjustment() || isDownPayment() || isInterestPaymentWaiver();
|| isChargeAdjustment() || isDownPayment() || isInterestPaymentWaiver() || isInterestRefund();
}
public boolean isTypeAllowedForChargeback() {
@@ -1118,6 +1123,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
return MathUtil.isGreaterThanZero(overPaymentPortion);
}
public boolean isInterestRefund() {
return getTypeOf().isInterestRefund();
}
// TODO missing hashCode(), equals(Object obj), but probably OK as long as
// this is never stored in a Collection.
}
@@ -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);
}
@@ -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);
}
}