FINERACT-2311: Buy down fee - daily amortization
This commit is contained in:
+3
-2
@@ -50,8 +50,9 @@ public class WorkFlowJobHelper {
|
||||
new BusinessStep().stepName("ADD_PERIODIC_ACCRUAL_ENTRIES").order(7L), //
|
||||
new BusinessStep().stepName("ACCRUAL_ACTIVITY_POSTING").order(8L), //
|
||||
new BusinessStep().stepName("CAPITALIZED_INCOME_AMORTIZATION").order(9L), //
|
||||
new BusinessStep().stepName("LOAN_INTEREST_RECALCULATION").order(10L), //
|
||||
new BusinessStep().stepName("EXTERNAL_ASSET_OWNER_TRANSFER").order(11L)//
|
||||
new BusinessStep().stepName("BUY_DOWN_FEE_AMORTIZATION").order(10L), //
|
||||
new BusinessStep().stepName("LOAN_INTEREST_RECALCULATION").order(11L), //
|
||||
new BusinessStep().stepName("EXTERNAL_ASSET_OWNER_TRANSFER").order(12L)//
|
||||
);
|
||||
BusinessStepRequest request = new BusinessStepRequest().businessSteps(businessSteps);
|
||||
Response<Void> response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request)
|
||||
|
||||
+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 LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent extends LoanTransactionBusinessEvent {
|
||||
|
||||
private static final String TYPE = "LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent";
|
||||
|
||||
public LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent(LoanTransaction transaction) {
|
||||
super(transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
+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 LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent extends LoanTransactionBusinessEvent {
|
||||
|
||||
private static final String TYPE = "LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent";
|
||||
|
||||
public LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent(LoanTransaction transaction) {
|
||||
super(transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
+2
@@ -57,5 +57,7 @@ public interface LoanTransactionApiConstants {
|
||||
capitalizedIncomeAdjustment, //
|
||||
contractTermination, //
|
||||
capitalizedIncomeAmortizationAdjustment, //
|
||||
buyDownFeeAmortization, //
|
||||
buyDownFeeAmortizationAdjustment, //
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -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() {
|
||||
|
||||
+23
-1
@@ -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) {
|
||||
|
||||
+14
-3
@@ -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
|
||||
|
||||
+4
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
+28
@@ -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);
|
||||
}
|
||||
+6
@@ -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");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+159
@@ -153,6 +153,14 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
if (transactionType.isBuyDownFeeAdjustment()) {
|
||||
createJournalEntriesForBuyDownFeeAdjustment(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
// Handle Buy Down Fee Amortization
|
||||
if (transactionType.isBuyDownFeeAmortization()) {
|
||||
createJournalEntriesForBuyDownFeeAmortization(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
// Handle Buy Down Fee Amortization Adjustment
|
||||
if (transactionType.isBuyDownFeeAmortizationAdjustment()) {
|
||||
createJournalEntriesForBuyDownFeeAmortizationAdjustment(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,6 +470,157 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForBuyDownFeeAmortization(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
|
||||
final Office office) {
|
||||
final boolean isMarkedAsChargeOff = loanDTO.isMarkedAsChargeOff();
|
||||
if (isMarkedAsChargeOff) {
|
||||
createJournalEntriesForChargeOffLoanBuyDownFeeAmortization(loanDTO, loanTransactionDTO, office);
|
||||
} else {
|
||||
createJournalEntriesForLoanBuyDownFeeAmortization(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForLoanBuyDownFeeAmortization(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
|
||||
final Office office) {
|
||||
// loan properties
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
final Long loanId = loanDTO.getLoanId();
|
||||
final String currencyCode = loanDTO.getCurrencyCode();
|
||||
final boolean isLoanWrittenOff = loanDTO.isMarkedAsWrittenOff();
|
||||
|
||||
// transaction properties
|
||||
final String transactionId = loanTransactionDTO.getTransactionId();
|
||||
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
|
||||
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
|
||||
final BigDecimal feesAmount = loanTransactionDTO.getFees();
|
||||
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
||||
final GLAccountBalanceHolder glAccountBalanceHolder = new GLAccountBalanceHolder();
|
||||
|
||||
// interest payment
|
||||
final AccrualAccountsForLoan creditAccountType = isLoanWrittenOff ? AccrualAccountsForLoan.LOSSES_WRITTEN_OFF
|
||||
: AccrualAccountsForLoan.INCOME_FROM_BUY_DOWN;
|
||||
if (MathUtil.isGreaterThanZero(interestAmount)) {
|
||||
populateCreditDebitMaps(loanProductId, interestAmount, paymentTypeId, creditAccountType.getValue(),
|
||||
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), glAccountBalanceHolder);
|
||||
}
|
||||
// handle fees payment
|
||||
if (MathUtil.isGreaterThanZero(feesAmount)) {
|
||||
populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, creditAccountType.getValue(),
|
||||
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), glAccountBalanceHolder);
|
||||
}
|
||||
|
||||
// create credit entries
|
||||
for (Map.Entry<Long, BigDecimal> creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) {
|
||||
if (MathUtil.isGreaterThanZero(creditEntry.getValue())) {
|
||||
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
|
||||
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
|
||||
creditEntry.getValue(), glAccount);
|
||||
}
|
||||
}
|
||||
// create debit entries
|
||||
for (Map.Entry<Long, BigDecimal> debitEntry : glAccountBalanceHolder.getDebitBalances().entrySet()) {
|
||||
if (MathUtil.isGreaterThanZero(debitEntry.getValue())) {
|
||||
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
|
||||
debitEntry.getValue(), glAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForChargeOffLoanBuyDownFeeAmortization(final LoanDTO loanDTO,
|
||||
final LoanTransactionDTO loanTransactionDTO, final Office office) {
|
||||
// loan properties
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
final boolean isMarkedFraud = loanDTO.isMarkedAsFraud();
|
||||
final Long loanId = loanDTO.getLoanId();
|
||||
final String currencyCode = loanDTO.getCurrencyCode();
|
||||
|
||||
// transaction properties
|
||||
final String transactionId = loanTransactionDTO.getTransactionId();
|
||||
final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
|
||||
final BigDecimal interestAmount = loanTransactionDTO.getInterest();
|
||||
final BigDecimal feesAmount = loanTransactionDTO.getFees();
|
||||
final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
|
||||
final GLAccountBalanceHolder glAccountBalanceHolder = new GLAccountBalanceHolder();
|
||||
final Long chargeOffReasonCodeValue = loanDTO.getChargeOffReasonCodeValue();
|
||||
|
||||
final ProductToGLAccountMapping mapping = chargeOffReasonCodeValue != null
|
||||
? helper.getChargeOffMappingByCodeValue(loanProductId, PortfolioProductType.LOAN, chargeOffReasonCodeValue)
|
||||
: null;
|
||||
|
||||
if (mapping != null) {
|
||||
final GLAccount accountDebit = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), paymentTypeId);
|
||||
// handle interest payment
|
||||
if (MathUtil.isGreaterThanZero(interestAmount)) {
|
||||
glAccountBalanceHolder.addToCredit(mapping.getGlAccount(), interestAmount);
|
||||
glAccountBalanceHolder.addToDebit(accountDebit, interestAmount);
|
||||
}
|
||||
// handle fees payment
|
||||
if (MathUtil.isGreaterThanZero(feesAmount)) {
|
||||
glAccountBalanceHolder.addToCredit(mapping.getGlAccount(), feesAmount);
|
||||
glAccountBalanceHolder.addToDebit(accountDebit, feesAmount);
|
||||
}
|
||||
} else {
|
||||
final AccrualAccountsForLoan creditAccountType = isMarkedFraud ? AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE
|
||||
: AccrualAccountsForLoan.CHARGE_OFF_EXPENSE;
|
||||
// handle interest payment
|
||||
if (MathUtil.isGreaterThanZero(interestAmount)) {
|
||||
populateCreditDebitMaps(loanProductId, interestAmount, paymentTypeId, creditAccountType.getValue(),
|
||||
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), glAccountBalanceHolder);
|
||||
}
|
||||
// handle fees payment
|
||||
if (MathUtil.isGreaterThanZero(feesAmount)) {
|
||||
populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, creditAccountType.getValue(),
|
||||
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), glAccountBalanceHolder);
|
||||
}
|
||||
}
|
||||
|
||||
// create credit entries
|
||||
for (Map.Entry<Long, BigDecimal> creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) {
|
||||
if (MathUtil.isGreaterThanZero(creditEntry.getValue())) {
|
||||
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
|
||||
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
|
||||
creditEntry.getValue(), glAccount);
|
||||
}
|
||||
}
|
||||
// create debit entries
|
||||
for (Map.Entry<Long, BigDecimal> debitEntry : glAccountBalanceHolder.getDebitBalances().entrySet()) {
|
||||
if (MathUtil.isGreaterThanZero(debitEntry.getValue())) {
|
||||
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
|
||||
debitEntry.getValue(), glAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForBuyDownFeeAmortizationAdjustment(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
|
||||
final Office office) {
|
||||
GLAccountBalanceHolder glAccountBalanceHolder = new GLAccountBalanceHolder();
|
||||
if (MathUtil.isGreaterThanZero(loanTransactionDTO.getAmount())) {
|
||||
populateCreditDebitMaps(loanDTO.getLoanProductId(), loanTransactionDTO.getAmount(), loanTransactionDTO.getPaymentTypeId(),
|
||||
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), AccrualAccountsForLoan.INCOME_FROM_BUY_DOWN.getValue(),
|
||||
glAccountBalanceHolder);
|
||||
}
|
||||
|
||||
// create credit entries
|
||||
for (Map.Entry<Long, BigDecimal> creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) {
|
||||
if (MathUtil.isGreaterThanZero(creditEntry.getValue())) {
|
||||
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
|
||||
this.helper.createCreditJournalEntryForLoan(office, loanDTO.getCurrencyCode(), loanDTO.getLoanId(),
|
||||
loanTransactionDTO.getTransactionId(), loanTransactionDTO.getTransactionDate(), creditEntry.getValue(), glAccount);
|
||||
}
|
||||
}
|
||||
// create debit entries
|
||||
for (Map.Entry<Long, BigDecimal> debitEntry : glAccountBalanceHolder.getDebitBalances().entrySet()) {
|
||||
if (MathUtil.isGreaterThanZero(debitEntry.getValue())) {
|
||||
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
|
||||
this.helper.createDebitJournalEntryForLoan(office, loanDTO.getCurrencyCode(), loanDTO.getLoanId(),
|
||||
loanTransactionDTO.getTransactionId(), loanTransactionDTO.getTransactionDate(), debitEntry.getValue(), glAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForInterestPaymentWaiverOrInterestRefund(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO,
|
||||
Office office) {
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.cob.loan;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.time.LocalDate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBuyDownFeeAmortizationProcessingService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class BuyDownFeeAmortizationBusinessStep implements LoanCOBBusinessStep {
|
||||
|
||||
private final LoanBuyDownFeeAmortizationProcessingService loanBuyDownFeeAmortizationProcessingService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public Loan execute(Loan loan) {
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableBuyDownFee()) {
|
||||
return loan;
|
||||
}
|
||||
|
||||
LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
||||
|
||||
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationTillDate(loan, businessDate, true);
|
||||
|
||||
return loan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEnumStyledName() {
|
||||
return "BUY_DOWN_FEE_AMORTIZATION";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableName() {
|
||||
return "Buy Down Fee amortization";
|
||||
}
|
||||
}
|
||||
+2
@@ -537,6 +537,8 @@ public class LoanTransactionsApiResource {
|
||||
case capitalizedIncomeAdjustment -> LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT;
|
||||
case contractTermination -> LoanTransactionType.CONTRACT_TERMINATION;
|
||||
case capitalizedIncomeAmortizationAdjustment -> LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT;
|
||||
case buyDownFeeAmortization -> LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION;
|
||||
case buyDownFeeAmortizationAdjustment -> LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT;
|
||||
default ->
|
||||
throw new InvalidLoanTransactionTypeException("transaction", transactionTypeParam.name(), "Unknown transaction type");
|
||||
};
|
||||
|
||||
+8
@@ -735,6 +735,14 @@ final class LoansApiResourceSwagger {
|
||||
public boolean capitalizedIncomeAdjustment;
|
||||
@Schema(example = "false")
|
||||
public boolean contractTermination;
|
||||
@Schema(example = "false")
|
||||
public boolean buyDownFee;
|
||||
@Schema(example = "false")
|
||||
public boolean buyDownFeeAdjustment;
|
||||
@Schema(example = "false")
|
||||
public boolean buyDownFeeAmortization;
|
||||
@Schema(example = "false")
|
||||
public boolean buyDownFeeAmortizationAdjustment;
|
||||
}
|
||||
|
||||
static final class GetLoansLoanIdPaymentDetailData {
|
||||
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeesBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.util.BuyDownFeeAmortizationUtil;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyDownFeeAmortizationProcessingService {
|
||||
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final LoanBuyDownFeesBalanceRepository loanBuyDownFeesBalanceRepository;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanJournalEntryPoster journalEntryPoster;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processBuyDownFeeAmortizationTillDate(@NonNull Loan loan, @NonNull LocalDate tillDate, boolean addJournal) {
|
||||
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
|
||||
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
|
||||
|
||||
List<LoanBuyDownFeeBalance> balances = loanBuyDownFeesBalanceRepository.findAllByLoanId(loan.getId());
|
||||
|
||||
LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate()
|
||||
: getFinalBuyDownFeeAmortizationTransactionDate(loan);
|
||||
LocalDate tillDatePlusOne = tillDate.plusDays(1);
|
||||
if (tillDatePlusOne.isAfter(maturityDate)) {
|
||||
tillDatePlusOne = maturityDate;
|
||||
}
|
||||
|
||||
Money totalAmortization = Money.zero(loan.getCurrency());
|
||||
for (LoanBuyDownFeeBalance balance : balances) {
|
||||
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
|
||||
Money amortizationTillDate = BuyDownFeeAmortizationUtil.calculateTotalAmortizationTillDate(balance, adjustments, maturityDate,
|
||||
loan.getLoanProductRelatedDetail().getBuyDownFeeStrategy(), tillDatePlusOne, loan.getCurrency());
|
||||
totalAmortization = totalAmortization.add(amortizationTillDate);
|
||||
|
||||
balance.setUnrecognizedAmount(balance.getAmount().subtract(MathUtil.nullToZero(balance.getAmountAdjustment()))
|
||||
.subtract(amortizationTillDate.getAmount()));
|
||||
}
|
||||
|
||||
loanBuyDownFeesBalanceRepository.saveAll(balances);
|
||||
|
||||
BigDecimal totalAmortized = loanTransactionRepository.getAmortizedAmountBuyDownFee(loan);
|
||||
BigDecimal totalAmortizationAmount = totalAmortization.getAmount().subtract(totalAmortized);
|
||||
|
||||
if (!MathUtil.isZero(totalAmortizationAmount)) {
|
||||
LoanTransaction transaction = MathUtil.isGreaterThanZero(totalAmortizationAmount)
|
||||
? LoanTransaction.buyDownFeeAmortization(loan, loan.getOffice(), tillDate, totalAmortizationAmount,
|
||||
externalIdFactory.create())
|
||||
: LoanTransaction.buyDownFeeAmortizationAdjustment(loan,
|
||||
Money.of(loan.getCurrency(), MathUtil.negate(totalAmortizationAmount)), tillDate, externalIdFactory.create());
|
||||
loan.addLoanTransaction(transaction);
|
||||
|
||||
transaction = loanTransactionRepository.save(transaction);
|
||||
loanTransactionRepository.flush();
|
||||
|
||||
if (addJournal) {
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
|
||||
BusinessEvent<?> event = MathUtil.isGreaterThanZero(totalAmortizationAmount)
|
||||
? new LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent(transaction)
|
||||
: new LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent(transaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate getFinalBuyDownFeeAmortizationTransactionDate(final Loan loan) {
|
||||
return switch (loan.getStatus()) {
|
||||
case CLOSED_OBLIGATIONS_MET -> loan.getClosedOnDate();
|
||||
case OVERPAID -> loan.getOverpaidOnDate();
|
||||
case CLOSED_WRITTEN_OFF -> loan.getWrittenOffOnDate();
|
||||
default -> throw new IllegalStateException("Unexpected value: " + loan.getStatus());
|
||||
};
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -125,7 +125,7 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
|
||||
|
||||
BigDecimal totalAmortizationAmount = BigDecimal.ZERO;
|
||||
for (LoanCapitalizedIncomeBalance balance : balances) {
|
||||
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustmentsForCapitalizedIncome(balance.getLoanTransaction());
|
||||
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
|
||||
LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate() : transactionDate;
|
||||
final Money amortizationTillDate = CapitalizedIncomeAmortizationUtil.calculateTotalAmortizationTillDate(balance, adjustments,
|
||||
maturityDate, loan.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy(), maturityDate, loan.getCurrency());
|
||||
@@ -136,7 +136,7 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
|
||||
balance.setUnrecognizedAmount(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
BigDecimal amortizedAmount = loanTransactionRepository.getAmortizedAmount(loan);
|
||||
BigDecimal amortizedAmount = loanTransactionRepository.getAmortizedAmountCapitalizedIncome(loan);
|
||||
BigDecimal totalUnrecognizedAmount = totalAmortizationAmount.subtract(amortizedAmount);
|
||||
if (MathUtil.isZero(totalUnrecognizedAmount)) {
|
||||
return Optional.empty();
|
||||
@@ -203,7 +203,7 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
|
||||
|
||||
Money totalAmortization = Money.zero(loan.getCurrency());
|
||||
for (LoanCapitalizedIncomeBalance balance : balances) {
|
||||
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustmentsForCapitalizedIncome(balance.getLoanTransaction());
|
||||
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
|
||||
Money amortizationTillDate = CapitalizedIncomeAmortizationUtil.calculateTotalAmortizationTillDate(balance, adjustments,
|
||||
maturityDate, loan.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy(), tillDatePlusOne, loan.getCurrency());
|
||||
totalAmortization = totalAmortization.add(amortizationTillDate);
|
||||
@@ -214,7 +214,7 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
|
||||
|
||||
loanCapitalizedIncomeBalanceRepository.saveAll(balances);
|
||||
|
||||
BigDecimal totalAmortized = loanTransactionRepository.getAmortizedAmount(loan);
|
||||
BigDecimal totalAmortized = loanTransactionRepository.getAmortizedAmountCapitalizedIncome(loan);
|
||||
BigDecimal totalAmortizationAmount = totalAmortization.getAmount().subtract(totalAmortized);
|
||||
|
||||
if (!MathUtil.isZero(totalAmortizationAmount)) {
|
||||
|
||||
+24
@@ -123,9 +123,12 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssemblerImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBuyDownFeeAmortizationProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBuyDownFeeAmortizationProcessingServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationEventService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationProcessingServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
||||
@@ -549,4 +552,25 @@ public class LoanAccountConfiguration {
|
||||
loanCapitalizedIncomeAmortizationProcessingService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanCapitalizedIncomeAmortizationProcessingService.class)
|
||||
public LoanCapitalizedIncomeAmortizationProcessingService loanCapitalizedIncomeAmortizationProcessingService(
|
||||
final ConfigurationDomainService configurationDomainService, final LoanTransactionRepository loanTransactionRepository,
|
||||
final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository,
|
||||
final BusinessEventNotifierService businessEventNotifierService, final LoanJournalEntryPoster journalEntryPoster,
|
||||
final ExternalIdFactory externalIdFactory) {
|
||||
return new LoanCapitalizedIncomeAmortizationProcessingServiceImpl(configurationDomainService, loanTransactionRepository,
|
||||
loanCapitalizedIncomeBalanceRepository, businessEventNotifierService, journalEntryPoster, externalIdFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanBuyDownFeeAmortizationProcessingService.class)
|
||||
public LoanBuyDownFeeAmortizationProcessingService loanBuyDownFeeAmortizationProcessingService(
|
||||
final LoanTransactionRepository loanTransactionRepository,
|
||||
final LoanBuyDownFeesBalanceRepository loanBuyDownFeesBalanceRepository,
|
||||
final BusinessEventNotifierService businessEventNotifierService, final LoanJournalEntryPoster journalEntryPoster,
|
||||
final ExternalIdFactory externalIdFactory) {
|
||||
return new LoanBuyDownFeeAmortizationProcessingServiceImpl(loanTransactionRepository, loanBuyDownFeesBalanceRepository,
|
||||
businessEventNotifierService, journalEntryPoster, externalIdFactory);
|
||||
}
|
||||
}
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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.util;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
|
||||
public final class BuyDownFeeAmortizationUtil {
|
||||
|
||||
private BuyDownFeeAmortizationUtil() {}
|
||||
|
||||
public static Money calculateTotalAmortizationTillDate(final LoanBuyDownFeeBalance buyDownFeeBalance,
|
||||
final List<LoanTransaction> adjustmentTransactions, final LocalDate maturityDate,
|
||||
final LoanBuyDownFeeStrategy buyDownFeeStrategy, final LocalDate tillDate, final MonetaryCurrency currency) {
|
||||
return switch (buyDownFeeStrategy) {
|
||||
case EQUAL_AMORTIZATION -> calculateTotalAmortizationTillDateEqualAmortization(buyDownFeeBalance, adjustmentTransactions,
|
||||
maturityDate, tillDate, currency);
|
||||
};
|
||||
}
|
||||
|
||||
private static Money calculateTotalAmortizationTillDateEqualAmortization(LoanBuyDownFeeBalance balance,
|
||||
List<LoanTransaction> adjustmentTransactions, LocalDate maturityDate, LocalDate tillDate, MonetaryCurrency currency) {
|
||||
|
||||
BigDecimal unrecognizedAmount = balance.getAmount();
|
||||
BigDecimal totalAmortizationAmount = BigDecimal.ZERO;
|
||||
BigDecimal overAmortizationCorrection = BigDecimal.ZERO;
|
||||
|
||||
List<LoanTransaction> sortedAdjustmentTransactions = adjustmentTransactions.stream()
|
||||
.sorted(Comparator.comparing(LoanTransaction::getDateOf)).toList();
|
||||
LocalDate periodStart = balance.getDate();
|
||||
for (LoanTransaction adjustmentTransaction : sortedAdjustmentTransactions) {
|
||||
long daysUntilMaturity = DateUtils.getDifferenceInDays(periodStart, maturityDate);
|
||||
long daysOfPeriod = DateUtils.getDifferenceInDays(periodStart, adjustmentTransaction.getDateOf());
|
||||
BigDecimal periodAmortization = daysUntilMaturity == 0L ? BigDecimal.ZERO
|
||||
: unrecognizedAmount.multiply(BigDecimal.valueOf(daysOfPeriod)).divide(BigDecimal.valueOf(daysUntilMaturity),
|
||||
MoneyHelper.getMathContext());
|
||||
|
||||
totalAmortizationAmount = totalAmortizationAmount.add(periodAmortization);
|
||||
unrecognizedAmount = unrecognizedAmount.subtract(periodAmortization).subtract(adjustmentTransaction.getAmount());
|
||||
if (MathUtil.isLessThanZero(unrecognizedAmount)) {
|
||||
overAmortizationCorrection = overAmortizationCorrection.add(unrecognizedAmount);
|
||||
unrecognizedAmount = BigDecimal.ZERO;
|
||||
}
|
||||
periodStart = adjustmentTransaction.getDateOf();
|
||||
}
|
||||
if (periodStart.isBefore(tillDate)) {
|
||||
long daysUntilMaturity = DateUtils.getDifferenceInDays(periodStart, maturityDate);
|
||||
long daysOfPeriod = DateUtils.getDifferenceInDays(periodStart, tillDate);
|
||||
BigDecimal periodAmortization = unrecognizedAmount.multiply(BigDecimal.valueOf(daysOfPeriod))
|
||||
.divide(BigDecimal.valueOf(daysUntilMaturity), MoneyHelper.getMathContext());
|
||||
totalAmortizationAmount = totalAmortizationAmount.add(periodAmortization);
|
||||
} else if (balance.getDate().equals(maturityDate)) {
|
||||
totalAmortizationAmount = totalAmortizationAmount.add(unrecognizedAmount);
|
||||
}
|
||||
|
||||
return Money.of(currency, totalAmortizationAmount.add(overAmortizationCorrection));
|
||||
}
|
||||
}
|
||||
@@ -208,4 +208,5 @@
|
||||
<include file="parts/0187_add_Loan_modified_and_withdrawn_event_configuration.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0188_create_loan_buy_down_fee_balance.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0189_add_loan_buydown_fee_event.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0190_buy_down_fee_amortization.xml" relativeToChangelogFile="true" />
|
||||
</databaseChangeLog>
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?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.1.xsd">
|
||||
<changeSet id="1" author="fineract">
|
||||
<insert tableName="m_external_event_configuration">
|
||||
<column name="type" value="LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent"/>
|
||||
<column name="enabled" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_external_event_configuration">
|
||||
<column name="type" value="LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent"/>
|
||||
<column name="enabled" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="r_enum_value">
|
||||
<column name="enum_name" value="transaction_type_enum"/>
|
||||
<column name="enum_id" valueNumeric="42"/>
|
||||
<column name="enum_message_property" value="Buy Down Fee Amortization"/>
|
||||
<column name="enum_value" value="Buy Down Fee Amortization"/>
|
||||
<column name="enum_type" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="r_enum_value">
|
||||
<column name="enum_name" value="transaction_type_enum"/>
|
||||
<column name="enum_id" valueNumeric="43"/>
|
||||
<column name="enum_message_property" value="Buy Down Fee Amortization Adjustment"/>
|
||||
<column name="enum_value" value="Buy Down Fee Amortization Adjustment"/>
|
||||
<column name="enum_type" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+6
-2
@@ -110,7 +110,9 @@ public class ExternalEventConfigurationValidationServiceTest {
|
||||
"LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent", "LoanTransactionContractTerminationPostBusinessEvent",
|
||||
"LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent",
|
||||
"LoanCapitalizedIncomeTransactionCreatedBusinessEvent", "LoanUndoContractTerminationBusinessEvent",
|
||||
"LoanBuyDownFeeTransactionCreatedBusinessEvent", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent");
|
||||
"LoanBuyDownFeeTransactionCreatedBusinessEvent", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent",
|
||||
"LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent",
|
||||
"LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent");
|
||||
|
||||
List<FineractPlatformTenant> tenants = Arrays
|
||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||
@@ -202,7 +204,9 @@ public class ExternalEventConfigurationValidationServiceTest {
|
||||
"LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent", "LoanTransactionContractTerminationPostBusinessEvent",
|
||||
"LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent",
|
||||
"LoanCapitalizedIncomeTransactionCreatedBusinessEvent", "LoanUndoContractTerminationBusinessEvent",
|
||||
"LoanBuyDownFeeTransactionCreatedBusinessEvent", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent");
|
||||
"LoanBuyDownFeeTransactionCreatedBusinessEvent", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent",
|
||||
"LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent",
|
||||
"LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent");
|
||||
|
||||
List<FineractPlatformTenant> tenants = Arrays
|
||||
.asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
|
||||
|
||||
+148
@@ -23,22 +23,28 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
||||
import org.apache.fineract.client.models.PostClientsResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
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.accounting.Account;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -425,4 +431,146 @@ public class LoanBuyDownFeeTest extends BaseLoanIntegrationTest {
|
||||
.transactionAmount(amount).externalId(buyDownFeeExternalId).note("Buy Down Fee Transaction"));
|
||||
return response.getResourceId();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuyDownFeeDailyAmortization() {
|
||||
new BusinessStepHelper().updateSteps("LOAN_CLOSE_OF_BUSINESS", "APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION",
|
||||
"CHECK_LOAN_REPAYMENT_DUE", "CHECK_LOAN_REPAYMENT_OVERDUE", "CHECK_DUE_INSTALLMENTS", "UPDATE_LOAN_ARREARS_AGING",
|
||||
"ADD_PERIODIC_ACCRUAL_ENTRIES", "ACCRUAL_ACTIVITY_POSTING", "CAPITALIZED_INCOME_AMORTIZATION", "BUY_DOWN_FEE_AMORTIZATION",
|
||||
"LOAN_INTEREST_RECALCULATION", "EXTERNAL_ASSET_OWNER_TRANSFER");
|
||||
|
||||
final AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||
final AtomicReference<Long> buyDownFeeTransactionIdIdRef = new AtomicReference<>();
|
||||
|
||||
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||
|
||||
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive()
|
||||
.enableBuyDownFee(true).buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)
|
||||
.buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)
|
||||
.buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE)
|
||||
.buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue())
|
||||
.deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue())
|
||||
.incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue()));
|
||||
|
||||
runAt("1 January 2024", () -> {
|
||||
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
|
||||
500.0, 7.0, 3, null);
|
||||
loanIdRef.set(loanId);
|
||||
|
||||
disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024");
|
||||
PostLoansLoanIdTransactionsResponse transactionsResponse = loanTransactionHelper.makeLoanBuyDownFee(loanId, "1 January 2024",
|
||||
50.0);
|
||||
buyDownFeeTransactionIdIdRef.set(transactionsResponse.getResourceId());
|
||||
});
|
||||
runAt("31 January 2024", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
executeInlineCOB(loanId);
|
||||
|
||||
// summarized amortization
|
||||
verifyTransactions(loanId, //
|
||||
transaction(100.0, "Disbursement", "01 January 2024"), //
|
||||
transaction(0.55, "Accrual", "30 January 2024"), //
|
||||
transaction(50.0, "Buy Down Fee", "01 January 2024"), //
|
||||
transaction(16.48, "Buy Down Fee Amortization", "30 January 2024"));
|
||||
});
|
||||
runAt("1 February 2024", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
executeInlineCOB(loanId);
|
||||
|
||||
// daily amortization
|
||||
verifyTransactions(loanId, //
|
||||
transaction(100.0, "Disbursement", "01 January 2024"), //
|
||||
transaction(50.0, "Buy Down Fee", "01 January 2024"), //
|
||||
transaction(0.55, "Accrual", "30 January 2024"), //
|
||||
transaction(16.48, "Buy Down Fee Amortization", "30 January 2024"), //
|
||||
transaction(0.01, "Accrual", "31 January 2024"), //
|
||||
transaction(0.55, "Buy Down Fee Amortization", "31 January 2024"));
|
||||
|
||||
loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionIdIdRef.get(), "1 February 2024", 10.0);
|
||||
});
|
||||
runAt("2 February 2024", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
executeInlineCOB(loanId);
|
||||
|
||||
// not backdated and not large buy down fee adjustment -> lowered daily amount
|
||||
verifyTransactions(loanId, //
|
||||
transaction(100.0, "Disbursement", "01 January 2024"), //
|
||||
transaction(50.0, "Buy Down Fee", "01 January 2024"), //
|
||||
transaction(0.55, "Accrual", "30 January 2024"), //
|
||||
transaction(16.48, "Buy Down Fee Amortization", "30 January 2024"), //
|
||||
transaction(0.01, "Accrual", "31 January 2024"), //
|
||||
transaction(0.55, "Buy Down Fee Amortization", "31 January 2024"), //
|
||||
transaction(10.0, "Buy Down Fee Adjustment", "01 February 2024"), //
|
||||
transaction(0.02, "Accrual", "01 February 2024"), //
|
||||
transaction(0.39, "Buy Down Fee Amortization", "01 February 2024"));
|
||||
|
||||
loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionIdIdRef.get(), "10 January 2024", 10.0);
|
||||
});
|
||||
runAt("3 February 2024", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
executeInlineCOB(loanId);
|
||||
|
||||
// backdated buy down fee adjustment -> amortization adjustment
|
||||
verifyTransactions(loanId, //
|
||||
transaction(100.0, "Disbursement", "01 January 2024"), //
|
||||
transaction(50.0, "Buy Down Fee", "01 January 2024"), //
|
||||
transaction(10.0, "Buy Down Fee Adjustment", "10 January 2024"), //
|
||||
transaction(0.55, "Accrual", "30 January 2024"), //
|
||||
transaction(16.48, "Buy Down Fee Amortization", "30 January 2024"), //
|
||||
transaction(0.01, "Accrual", "31 January 2024"), //
|
||||
transaction(0.55, "Buy Down Fee Amortization", "31 January 2024"), //
|
||||
transaction(10.0, "Buy Down Fee Adjustment", "01 February 2024"), //
|
||||
transaction(0.02, "Accrual", "01 February 2024"), //
|
||||
transaction(0.39, "Buy Down Fee Amortization", "01 February 2024"), //
|
||||
transaction(0.02, "Accrual", "02 February 2024"), //
|
||||
transaction(2.55, "Buy Down Fee Amortization Adjustment", "02 February 2024"));
|
||||
|
||||
loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionIdIdRef.get(), "03 February 2024", 20.0);
|
||||
});
|
||||
runAt("4 February 2024", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
executeInlineCOB(loanId);
|
||||
|
||||
// large (more than remaining unrecognized (15.13)) buy down fee adjustment -> amortization adjustment
|
||||
verifyTransactions(loanId, //
|
||||
transaction(100.0, "Disbursement", "01 January 2024"), //
|
||||
transaction(50.0, "Buy Down Fee", "01 January 2024"), //
|
||||
transaction(10.0, "Buy Down Fee Adjustment", "10 January 2024"), //
|
||||
transaction(0.55, "Accrual", "30 January 2024"), //
|
||||
transaction(16.48, "Buy Down Fee Amortization", "30 January 2024"), //
|
||||
transaction(0.01, "Accrual", "31 January 2024"), //
|
||||
transaction(0.55, "Buy Down Fee Amortization", "31 January 2024"), //
|
||||
transaction(10.0, "Buy Down Fee Adjustment", "01 February 2024"), //
|
||||
transaction(0.02, "Accrual", "01 February 2024"), //
|
||||
transaction(0.39, "Buy Down Fee Amortization", "01 February 2024"), //
|
||||
transaction(0.02, "Accrual", "02 February 2024"), //
|
||||
transaction(2.55, "Buy Down Fee Amortization Adjustment", "02 February 2024"), //
|
||||
transaction(20.0, "Buy Down Fee Adjustment", "03 February 2024"), //
|
||||
transaction(0.02, "Accrual", "03 February 2024"), //
|
||||
transaction(4.87, "Buy Down Fee Amortization Adjustment", "03 February 2024"));
|
||||
|
||||
// Check journal entries of amortization and amortization adjustment
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
|
||||
Optional<GetLoansLoanIdTransactions> amortizationTransactionOpt = loanDetails.getTransactions().stream()
|
||||
.filter(transaction -> LocalDate.of(2024, 2, 1).equals(transaction.getDate())
|
||||
&& transaction.getType().getBuyDownFeeAmortization())
|
||||
.findFirst();
|
||||
Assertions.assertTrue(amortizationTransactionOpt.isPresent());
|
||||
|
||||
verifyTRJournalEntries(amortizationTransactionOpt.get().getId(), //
|
||||
journalEntry(0.39, feeIncomeAccount, "CREDIT"), //
|
||||
journalEntry(0.39, deferredIncomeLiabilityAccount, "DEBIT"));
|
||||
|
||||
Optional<GetLoansLoanIdTransactions> amortizationAdjustmentTransactionOpt = loanDetails.getTransactions().stream()
|
||||
.filter(transaction -> LocalDate.of(2024, 2, 3).equals(transaction.getDate())
|
||||
&& transaction.getType().getBuyDownFeeAmortizationAdjustment())
|
||||
.findFirst();
|
||||
Assertions.assertTrue(amortizationAdjustmentTransactionOpt.isPresent());
|
||||
|
||||
verifyTRJournalEntries(amortizationAdjustmentTransactionOpt.get().getId(), //
|
||||
journalEntry(4.87, deferredIncomeLiabilityAccount, "CREDIT"), //
|
||||
journalEntry(4.87, feeIncomeAccount, "DEBIT"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -646,6 +646,17 @@ public class ExternalEventConfigurationHelper {
|
||||
loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.put("enabled", false);
|
||||
defaults.add(loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent);
|
||||
|
||||
Map<String, Object> loanBuyDownFeeAmortizationTransactionCreatedBusinessEvent = new HashMap<>();
|
||||
loanBuyDownFeeAmortizationTransactionCreatedBusinessEvent.put("type", "LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent");
|
||||
loanBuyDownFeeAmortizationTransactionCreatedBusinessEvent.put("enabled", false);
|
||||
defaults.add(loanBuyDownFeeAmortizationTransactionCreatedBusinessEvent);
|
||||
|
||||
Map<String, Object> loanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent = new HashMap<>();
|
||||
loanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent.put("type",
|
||||
"LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent");
|
||||
loanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent.put("enabled", false);
|
||||
defaults.add(loanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent);
|
||||
|
||||
return defaults;
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -3083,6 +3083,11 @@ public class LoanTransactionHelper {
|
||||
return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction(loanId, request, "buyDownFee"));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse makeLoanBuyDownFee(Long loanId, String date, double amount) {
|
||||
return makeLoanBuyDownFee(loanId, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate(date)
|
||||
.locale("en").transactionAmount(amount));
|
||||
}
|
||||
|
||||
public List<AdvancedPaymentData> getAdvancedPaymentAllocationRules(final Integer loanId) {
|
||||
return Calls.ok(FineractClientHelper.getFineractClient().legacy.getAdvancedPaymentAllocationRulesOfLoan(loanId.longValue()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user