FINERACT-2311: Buy down fee - daily amortization

This commit is contained in:
adam.magyari
2025-07-08 12:38:02 +02:00
committed by Adam Saghy
parent 8a5300ef23
commit b8d6dfa794
24 changed files with 829 additions and 12 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 LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent extends LoanTransactionBusinessEvent {
private static final String TYPE = "LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent";
public LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent(LoanTransaction transaction) {
super(transaction);
}
@Override
public String getType() {
return TYPE;
}
}
@@ -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 LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent extends LoanTransactionBusinessEvent {
private static final String TYPE = "LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent";
public LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent(LoanTransaction transaction) {
super(transaction);
}
@Override
public String getType() {
return TYPE;
}
}
@@ -57,5 +57,7 @@ public interface LoanTransactionApiConstants {
capitalizedIncomeAdjustment, //
contractTermination, //
capitalizedIncomeAmortizationAdjustment, //
buyDownFeeAmortization, //
buyDownFeeAmortizationAdjustment, //
}
}
@@ -74,6 +74,8 @@ public class LoanTransactionEnumData implements Serializable {
private final boolean contractTermination;
private final boolean buyDownFee;
private final boolean buyDownFeeAdjustment;
private final boolean buyDownFeeAmortization;
private final boolean buyDownFeeAmortizationAdjustment;
public LoanTransactionEnumData(final Long id, final String code, final String value) {
this.id = id;
@@ -118,6 +120,9 @@ public class LoanTransactionEnumData implements Serializable {
this.contractTermination = Long.valueOf(LoanTransactionType.CONTRACT_TERMINATION.getValue()).equals(this.id);
this.buyDownFee = Long.valueOf(LoanTransactionType.BUY_DOWN_FEE.getValue()).equals(this.id);
this.buyDownFeeAdjustment = Long.valueOf(LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT.getValue()).equals(this.id);
this.buyDownFeeAmortization = Long.valueOf(LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION.getValue()).equals(this.id);
this.buyDownFeeAmortizationAdjustment = Long.valueOf(LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT.getValue())
.equals(this.id);
}
public boolean isRepaymentType() {
@@ -348,6 +348,26 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
};
}
public static LoanTransaction buyDownFeeAmortization(final Loan loan, final Office office, final LocalDate dateOf,
final BigDecimal amount, final ExternalId externalId) {
return switch (loan.getLoanProductRelatedDetail().getBuyDownFeeIncomeType()) {
case FEE -> new LoanTransaction(loan, office, LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION, dateOf, amount, null, null, amount,
null, null, false, null, externalId);
case INTEREST -> new LoanTransaction(loan, office, LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION, dateOf, amount, null, amount,
null, null, null, false, null, externalId);
};
}
public static LoanTransaction buyDownFeeAmortizationAdjustment(final Loan loan, final Money amount, final LocalDate transactionDate,
final ExternalId externalId) {
return switch (loan.getLoanProductRelatedDetail().getBuyDownFeeIncomeType()) {
case FEE -> new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT,
transactionDate, amount.getAmount(), null, null, amount.getAmount(), null, null, false, null, externalId);
case INTEREST -> new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT,
transactionDate, amount.getAmount(), null, amount.getAmount(), null, null, null, false, null, externalId);
};
}
public LoanTransaction copyTransactionPropertiesAndMappings() {
LoanTransaction newTransaction = copyTransactionProperties(this);
newTransaction.updateLoanTransactionToRepaymentScheduleMappings(loanTransactionToRepaymentScheduleMappings);
@@ -806,7 +826,9 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|| type == LoanTransactionType.WITHDRAW_TRANSFER || type == LoanTransactionType.CHARGE_OFF
|| type == LoanTransactionType.REAMORTIZE || type == LoanTransactionType.REAGE
|| type == LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION || type == LoanTransactionType.CONTRACT_TERMINATION
|| type == LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT);
|| type == LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT
|| type == LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION
|| type == LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT);
}
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
@@ -361,16 +361,27 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
AND (lt.typeOf = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION
OR lt.typeOf = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT)
""")
BigDecimal getAmortizedAmount(@Param("loan") Loan loan);
BigDecimal getAmortizedAmountCapitalizedIncome(@Param("loan") Loan loan);
@Query("""
SELECT COALESCE(SUM(CASE WHEN lt.typeOf = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION THEN lt.amount
WHEN lt.typeOf = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT THEN -lt.amount
ELSE 0 END), 0) FROM LoanTransaction lt
WHERE lt.loan = :loan
AND lt.reversed = false
AND (lt.typeOf = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION
OR lt.typeOf = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT)
""")
BigDecimal getAmortizedAmountBuyDownFee(@Param("loan") Loan loan);
@Query("""
SELECT lt FROM LoanTransaction lt, LoanTransactionRelation ltr
WHERE lt.reversed = false
AND lt = ltr.fromTransaction
AND ltr.toTransaction = :capitalizedIncome
AND ltr.toTransaction = :transaction
AND ltr.relationType = org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.ADJUSTMENT
""")
List<LoanTransaction> findAdjustmentsForCapitalizedIncome(@Param("capitalizedIncome") LoanTransaction capitalizedIncome);
List<LoanTransaction> findAdjustments(@Param("transaction") LoanTransaction transaction);
@Query("""
SELECT lt FROM LoanTransaction lt
@@ -76,6 +76,8 @@ public enum LoanTransactionType {
BUY_DOWN_FEE(40, "loanTransactionType.buyDownFee"), //
BUY_DOWN_FEE_ADJUSTMENT(41, "loanTransactionType.buyDownFeeAdjustment"), //
BUY_DOWN_FEE_AMORTIZATION(42, "loanTransactionType.buyDownFeeAmortization"), //
BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT(43, "loanTransactionType.buyDownFeeAmortizationAdjustment"), //
;
private final Integer value;
@@ -133,6 +135,8 @@ public enum LoanTransactionType {
case 39 -> LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT;
case 40 -> LoanTransactionType.BUY_DOWN_FEE;
case 41 -> LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT;
case 42 -> LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION;
case 43 -> LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT;
default -> LoanTransactionType.INVALID;
};
}
@@ -0,0 +1,28 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.service;
import java.time.LocalDate;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.springframework.lang.NonNull;
public interface LoanBuyDownFeeAmortizationProcessingService {
void processBuyDownFeeAmortizationTillDate(@NonNull Loan loan, @NonNull LocalDate tillDate, boolean addJournal);
}
@@ -342,6 +342,12 @@ public final class LoanEnumerations {
LoanTransactionType.BUY_DOWN_FEE.getCode(), "Buy Down Fee");
case BUY_DOWN_FEE_ADJUSTMENT -> new LoanTransactionEnumData(LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT.getValue().longValue(),
LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT.getCode(), "Buy Down Fee Adjustment");
case BUY_DOWN_FEE_AMORTIZATION ->
new LoanTransactionEnumData(LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION.getValue().longValue(),
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION.getCode(), "Buy Down Fee Amortization");
case BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT ->
new LoanTransactionEnumData(LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT.getValue().longValue(),
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT.getCode(), "Buy Down Fee Amortization Adjustment");
};
}