diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index 26100f390..aa74858ce 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -505,6 +505,7 @@ public class FineractProperties { private FineractTransactionProcessorItemProperties heavensFamily; private FineractTransactionProcessorItemProperties interestPrincipalPenaltiesFees; private FineractTransactionProcessorItemProperties principalInterestPenaltiesFees; + private FineractTransactionProcessorItemProperties feesPenaltiesInterestPrincipal; private FineractTransactionProcessorItemProperties rbiIndia; private FineractTransactionProcessorItemProperties duePenaltyFeeInterestPrincipalInAdvancePrincipalPenaltyFeeInterest; private FineractTransactionProcessorItemProperties duePenaltyInterestPrincipalFeeInAdvancePenaltyInterestPrincipalFee; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FeesPenaltyInterestPrincipalOrderLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FeesPenaltyInterestPrincipalOrderLoanRepaymentScheduleTransactionProcessor.java new file mode 100644 index 000000000..d615d2298 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FeesPenaltyInterestPrincipalOrderLoanRepaymentScheduleTransactionProcessor.java @@ -0,0 +1,189 @@ +/** + * 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.domain.transactionprocessor.impl; + +import java.time.LocalDate; +import java.util.List; +import java.util.Set; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; +import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; +import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService; + +/** + * {@link LoanRepaymentScheduleTransactionProcessor} that pays off components in order of fees, penalties, interest, + * then principal. + * + * Other formulas exist on fineract where you can choose 'Declining-Balance Interest Recalculation' which simply means, + * recalculate the interest component based on the how much principal is outstanding at a point in time; but this isnt + * trying to model that option only the basic one for now. + */ +@SuppressWarnings("unused") +public class FeesPenaltyInterestPrincipalOrderLoanRepaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor { + + public static final String STRATEGY_CODE = "fees-penalties-interest-principal-order-strategy"; + + public static final String STRATEGY_NAME = "Fees, Penalties, Interest, Principal Order"; + + public FeesPenaltyInterestPrincipalOrderLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory, + final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) { + super(externalIdFactory, loanChargeValidator, loanBalanceService); + } + + @Override + public String getCode() { + return STRATEGY_CODE; + } + + @Override + public String getName() { + return STRATEGY_NAME; + } + + /** + * For early/'in advance' repayments, pay off in the same way as on-time payments, interest first then principal. + */ + @Override + protected Money handleTransactionThatIsPaymentInAdvanceOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final List installments, final LoanTransaction loanTransaction, final Money paymentInAdvance, + List transactionMappings, Set charges) { + + return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, paymentInAdvance, transactionMappings, + charges); + } + + /** + * For late repayments, pay off in the same way as on-time payments, interest first then principal. + */ + @Override + protected Money handleTransactionThatIsALateRepaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final List installments, final LoanTransaction loanTransaction, + final Money transactionAmountUnprocessed, List transactionMappings, + Set charges) { + + return handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, loanTransaction, transactionAmountUnprocessed, + transactionMappings, charges); + } + + /** + * For normal on-time repayments, pays off interest first, then principal. + */ + @Override + protected Money handleTransactionThatIsOnTimePaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, + List transactionMappings, Set charges) { + + final LocalDate transactionDate = loanTransaction.getTransactionDate(); + final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); + Money transactionAmountRemaining = transactionAmountUnprocessed; + Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency()); + Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency()); + Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); + Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); + + if (loanTransaction.isChargesWaiver()) { + penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate, + loanTransaction.getPenaltyChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + + feeChargesPortion = currentInstallment.waiveFeeChargesComponent(transactionDate, + loanTransaction.getFeeChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + + } else if (loanTransaction.isInterestWaiver()) { + interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); + + loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); + } else if (loanTransaction.isChargePayment()) { + if (loanTransaction.isPenaltyPayment()) { + penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + } else { + feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + } + loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); + } else { + feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + + penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + + interestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); + + principalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); + + loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); + } + if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) { + transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment, + principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion)); + } + return transactionAmountRemaining; + } + + @Override + protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, + final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, + List transactionMappings) { + + final LocalDate transactionDate = loanTransaction.getTransactionDate(); + final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); + Money transactionAmountRemaining = transactionAmountUnprocessed; + Money principalPortion = Money.zero(currency); + Money interestPortion = Money.zero(currency); + Money feeChargesPortion = Money.zero(currency); + Money penaltyChargesPortion = Money.zero(currency); + + principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); + + if (transactionAmountRemaining.isGreaterThanZero()) { + interestPortion = currentInstallment.unpayInterestComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); + } + + if (transactionAmountRemaining.isGreaterThanZero()) { + penaltyChargesPortion = currentInstallment.unpayPenaltyChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + } + + if (transactionAmountRemaining.isGreaterThanZero()) { + feeChargesPortion = currentInstallment.unpayFeeChargesComponent(transactionDate, transactionAmountRemaining); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + } + loanTransaction.updateComponents(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion); + if (principalPortion.plus(interestPortion).plus(feeChargesPortion).plus(penaltyChargesPortion).isGreaterThanZero()) { + transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, currentInstallment, + principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion)); + } + return transactionAmountRemaining; + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/FeesPenaltiesInterestPrincipalLoanRepaymentScheduleTransactionProcessorCondition.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/FeesPenaltiesInterestPrincipalLoanRepaymentScheduleTransactionProcessorCondition.java new file mode 100644 index 000000000..e7f031cd0 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/FeesPenaltiesInterestPrincipalLoanRepaymentScheduleTransactionProcessorCondition.java @@ -0,0 +1,30 @@ +/** + * 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.starter; + +import org.apache.fineract.infrastructure.core.condition.PropertiesCondition; +import org.apache.fineract.infrastructure.core.config.FineractProperties; + +public class FeesPenaltiesInterestPrincipalLoanRepaymentScheduleTransactionProcessorCondition extends PropertiesCondition { + + @Override + protected boolean matches(FineractProperties properties) { + return properties.getLoan().getTransactionProcessor().getFeesPenaltiesInterestPrincipal().isEnabled(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java index be1fadf72..ed20155c8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java @@ -27,6 +27,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.EarlyPaymentLoanRepaymentScheduleTransactionProcessor; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FeesPenaltyInterestPrincipalOrderLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.HeavensFamilyLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor; @@ -98,6 +99,15 @@ public class LoanAccountAutoStarter { loanBalanceService); } + @Bean + @Conditional(FeesPenaltiesInterestPrincipalLoanRepaymentScheduleTransactionProcessorCondition.class) + public FeesPenaltyInterestPrincipalOrderLoanRepaymentScheduleTransactionProcessor feesPenaltyInterestPrincipalOrderLoanRepaymentScheduleTransactionProcessor( + final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator, + final LoanBalanceService loanBalanceService) { + return new FeesPenaltyInterestPrincipalOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, + loanBalanceService); + } + @Bean @Conditional(RBIIndiaLoanRepaymentScheduleTransactionProcessorCondition.class) public RBILoanRepaymentScheduleTransactionProcessor rbiLoanRepaymentScheduleTransactionProcessor( diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 020cdbd2f..833c2a0f5 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -170,6 +170,7 @@ fineract.loan.transactionprocessor.mifos-standard.enabled=${FINERACT_LOAN_TRANSA fineract.loan.transactionprocessor.heavensfamily.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_HEAVENSFAMILY_ENABLED:true} fineract.loan.transactionprocessor.interest-principal-penalties-fees.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_INTEREST_PRINCIPAL_PENALTIES_FEES_ENABLED:true} fineract.loan.transactionprocessor.principal-interest-penalties-fees.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_PRINCIPAL_INTEREST_PENALTIES_FEES_ENABLED:true} +fineract.loan.transactionprocessor.fees-penalties-interest-principal.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_FEES_PENALTIES_INTEREST_PRINCIPAL_ENABLED:true} fineract.loan.transactionprocessor.rbi-india.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_RBI_INDIA_ENABLED:true} fineract.loan.transactionprocessor.due-penalty-fee-interest-principal-in-advance-principal-penalty-fee-interest.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_ENABLED:true} fineract.loan.transactionprocessor.due-penalty-interest-principal-fee-in-advance-penalty-interest-principal-fee.enabled=${FINERACT_LOAN_TRANSACTIONPROCESSOR_DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_ENABLED:true} diff --git a/fineract-provider/src/test/resources/application-test.properties b/fineract-provider/src/test/resources/application-test.properties index 541be1632..f78e5a82e 100644 --- a/fineract-provider/src/test/resources/application-test.properties +++ b/fineract-provider/src/test/resources/application-test.properties @@ -87,6 +87,7 @@ fineract.loan.transactionprocessor.mifos-standard.enabled=true fineract.loan.transactionprocessor.heavensfamily.enabled=true fineract.loan.transactionprocessor.interest-principal-penalties-fees.enabled=true fineract.loan.transactionprocessor.principal-interest-penalties-fees.enabled=true +fineract.loan.transactionprocessor.fees-penalties-interest-principal.enabled=true fineract.loan.transactionprocessor.rbi-india.enabled=true fineract.loan.transactionprocessor.due-penalty-fee-interest-principal-in-advance-principal-penalty-fee-interest.enabled=true fineract.loan.transactionprocessor.due-penalty-interest-principal-fee-in-advance-penalty-interest-principal-fee.enabled=true