This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 24cb62443 [FINERACT-1968] charge payment adv.paym.alloc.strategy
24cb62443 is described below
commit 24cb62443ed6b8691b46d6991aa401abbe2b10eb
Author: taskain7 <[email protected]>
AuthorDate: Thu Oct 26 17:18:28 2023 +0200
[FINERACT-1968] charge payment adv.paym.alloc.strategy
---
.../loanaccount/domain/LoanTransaction.java | 9 +-
.../domain/PaymentAllocationTransactionType.java | 3 +-
...dvancedPaymentScheduleTransactionProcessor.java | 275 ++++++++++------
...gePaymentWithAdvancedPaymentAllocationTest.java | 360 +++++++++++++++++++++
4 files changed, 546 insertions(+), 101 deletions(-)
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 07a5b2554..bca005632 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -275,10 +275,15 @@ public class LoanTransaction extends
AbstractAuditableWithUTCDateTimeCustom {
}
public static LoanTransaction copyTransactionProperties(final
LoanTransaction loanTransaction) {
- return new LoanTransaction(loanTransaction.loan,
loanTransaction.office, loanTransaction.typeOf, loanTransaction.dateOf,
- loanTransaction.amount, loanTransaction.principalPortion,
loanTransaction.interestPortion,
+ LoanTransaction newTransaction = new
LoanTransaction(loanTransaction.loan, loanTransaction.office,
loanTransaction.typeOf,
+ loanTransaction.dateOf, loanTransaction.amount,
loanTransaction.principalPortion, loanTransaction.interestPortion,
loanTransaction.feeChargesPortion,
loanTransaction.penaltyChargesPortion, loanTransaction.overPaymentPortion,
loanTransaction.reversed, loanTransaction.paymentDetail,
loanTransaction.externalId);
+
+ if
(LoanTransactionType.CHARGE_PAYMENT.equals(loanTransaction.getTypeOf())) {
+
newTransaction.getLoanChargesPaid().addAll(loanTransaction.getLoanChargesPaid());
+ }
+ return newTransaction;
}
public static LoanTransaction accrueLoanCharge(final Loan loan, final
Office office, final Money amount, final LocalDate applyDate,
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java
index adf967f6c..85b3d865d 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java
@@ -37,7 +37,8 @@ public enum PaymentAllocationTransactionType {
GOODWILL_CREDIT(LoanTransactionType.GOODWILL_CREDIT, "Goodwill credit"), //
CHARGE_REFUND(LoanTransactionType.CHARGE_REFUND, "Charge refund"), //
CHARGE_ADJUSTMENT(LoanTransactionType.CHARGE_ADJUSTMENT, "Charge
adjustment"), //
- WAIVE_INTEREST(LoanTransactionType.WAIVE_INTEREST, "Waive interest");//
+ WAIVE_INTEREST(LoanTransactionType.WAIVE_INTEREST, "Waive interest"),
CHARGE_PAYMENT(LoanTransactionType.CHARGE_PAYMENT,
+ "Charge payment");//
private final LoanTransactionType loanTransactionType;
private final String humanReadableName;
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 57fc21f19..7ab5a8a91 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -26,6 +26,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@@ -38,6 +39,7 @@ import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import
org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -183,11 +185,11 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
case WRITEOFF -> handleWriteOff(loanTransaction, currency,
installments);
case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction,
currency, installments, charges);
case CHARGEBACK -> handleChargeback(loanTransaction, currency,
overpaidAmount, installments);
-
case REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND,
GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT, DOWN_PAYMENT,
WAIVE_INTEREST, RECOVERY_REPAYMENT ->
handleRepayment(loanTransaction, currency, installments,
charges);
case CHARGE_OFF -> handleChargeOff(loanTransaction, currency,
installments);
+ case CHARGE_PAYMENT -> handleChargePayment(loanTransaction,
currency, installments, charges);
// TODO: Cover rest of the transaction types
default -> {
log.warn("Unhandled transaction processing for transaction
type: {}", loanTransaction.getTypeOf());
@@ -249,6 +251,153 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
loanTransaction.resetDerivedComponents();
}
Money transactionAmountUnprocessed =
loanTransaction.getAmount(currency);
+ processTransaction(loanTransaction, currency, installments,
transactionAmountUnprocessed, charges);
+ }
+
+ private LoanTransactionToRepaymentScheduleMapping getTransactionMapping(
+ List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, LoanTransaction loanTransaction,
+ LoanRepaymentScheduleInstallment currentInstallment,
MonetaryCurrency currency) {
+ Money zero = Money.zero(currency);
+ LoanTransactionToRepaymentScheduleMapping
loanTransactionToRepaymentScheduleMapping = transactionMappings.stream()
+ .filter(e -> loanTransaction.equals(e.getLoanTransaction()))
+ .filter(e ->
currentInstallment.equals(e.getLoanRepaymentScheduleInstallment())).findFirst().orElse(null);
+ if (loanTransactionToRepaymentScheduleMapping == null) {
+ loanTransactionToRepaymentScheduleMapping =
LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
+ currentInstallment, zero, zero, zero, zero);
+ transactionMappings.add(loanTransactionToRepaymentScheduleMapping);
+ }
+ return loanTransactionToRepaymentScheduleMapping;
+ }
+
+ private Money payAllocation(PaymentAllocationType paymentAllocationType,
LoanRepaymentScheduleInstallment currentInstallment,
+ LoanTransaction loanTransaction, Money
transactionAmountUnprocessed,
+ LoanTransactionToRepaymentScheduleMapping
loanTransactionToRepaymentScheduleMapping, Balances balances) {
+ LocalDate transactionDate = loanTransaction.getTransactionDate();
+ Money zero = transactionAmountUnprocessed.zero();
+ return switch (paymentAllocationType.getAllocationType()) {
+ case PENALTY -> {
+ Money subPenaltyPortion =
currentInstallment.payPenaltyChargesComponent(transactionDate,
transactionAmountUnprocessed);
+
balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().add(subPenaltyPortion));
+
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero,
zero, subPenaltyPortion);
+ yield subPenaltyPortion;
+ }
+ case FEE -> {
+ Money subFeePortion =
currentInstallment.payFeeChargesComponent(transactionDate,
transactionAmountUnprocessed);
+
balances.setAggregatedFeeChargesPortion(balances.getAggregatedFeeChargesPortion().add(subFeePortion));
+
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero,
subFeePortion, zero);
+ yield subFeePortion;
+ }
+ case INTEREST -> {
+ Money subInterestPortion =
currentInstallment.payInterestComponent(transactionDate,
transactionAmountUnprocessed);
+
balances.setAggregatedInterestPortion(balances.getAggregatedInterestPortion().add(subInterestPortion));
+
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero,
subInterestPortion, zero, zero);
+ yield subInterestPortion;
+ }
+ case PRINCIPAL -> {
+ Money subPrincipalPortion =
currentInstallment.payPrincipalComponent(transactionDate,
transactionAmountUnprocessed);
+
balances.setAggregatedPrincipalPortion(balances.getAggregatedPrincipalPortion().add(subPrincipalPortion));
+
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping,
subPrincipalPortion, zero, zero, zero);
+ yield subPrincipalPortion;
+ }
+ };
+ }
+
+ private void
addToTransactionMapping(LoanTransactionToRepaymentScheduleMapping
loanTransactionToRepaymentScheduleMapping,
+ Money principalPortion, Money interestPortion, Money feePortion,
Money penaltyPortion) {
+ BigDecimal aggregatedPenalty = ObjectUtils
+
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPenaltyChargesPortion(),
BigDecimal.ZERO)
+ .add(penaltyPortion.getAmount());
+ BigDecimal aggregatedFee = ObjectUtils
+
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getFeeChargesPortion(),
BigDecimal.ZERO)
+ .add(feePortion.getAmount());
+ BigDecimal aggregatedInterest = ObjectUtils
+
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getInterestPortion(),
BigDecimal.ZERO)
+ .add(interestPortion.getAmount());
+ BigDecimal aggregatedPrincipal = ObjectUtils
+
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPrincipalPortion(),
BigDecimal.ZERO)
+ .add(principalPortion.getAmount());
+
loanTransactionToRepaymentScheduleMapping.setComponents(aggregatedPrincipal,
aggregatedInterest, aggregatedFee, aggregatedPenalty);
+ }
+
+ private void handleOverpayment(Money overpaymentPortion, LoanTransaction
loanTransaction) {
+ if (overpaymentPortion.isGreaterThanZero()) {
+ onLoanOverpayment(loanTransaction, overpaymentPortion);
+ loanTransaction.updateOverPayments(overpaymentPortion);
+ }
+ }
+
+ private void handleChargeOff(LoanTransaction loanTransaction,
MonetaryCurrency currency,
+ List<LoanRepaymentScheduleInstallment> installments) {
+ loanTransaction.resetDerivedComponents();
+ // determine how much is outstanding total and breakdown for
principal, interest and charges
+ Money principalPortion = Money.zero(currency);
+ Money interestPortion = Money.zero(currency);
+ Money feeChargesPortion = Money.zero(currency);
+ Money penaltychargesPortion = Money.zero(currency);
+ for (final LoanRepaymentScheduleInstallment currentInstallment :
installments) {
+ if (currentInstallment.isNotFullyPaidOff()) {
+ principalPortion =
principalPortion.plus(currentInstallment.getPrincipalOutstanding(currency));
+ interestPortion =
interestPortion.plus(currentInstallment.getInterestOutstanding(currency));
+ feeChargesPortion =
feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(currency));
+ penaltychargesPortion =
penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency));
+ }
+ }
+
+ loanTransaction.updateComponentsAndTotal(principalPortion,
interestPortion, feeChargesPortion, penaltychargesPortion);
+ }
+
+ private void handleChargePayment(LoanTransaction loanTransaction,
MonetaryCurrency currency,
+ List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges) {
+ Money zero = Money.zero(currency);
+ Money feeChargesPortion = zero;
+ Money penaltyChargesPortion = zero;
+ List<LoanTransactionToRepaymentScheduleMapping> transactionMappings =
new ArrayList<>();
+ LoanChargePaidBy loanChargePaidBy =
loanTransaction.getLoanChargesPaid().stream().findFirst().get();
+ LoanCharge loanCharge = loanChargePaidBy.getLoanCharge();
+ Money amountToBePaid = Money.of(currency, loanTransaction.getAmount());
+ if
(loanCharge.getAmountOutstanding(currency).isLessThan(amountToBePaid)) {
+ amountToBePaid = loanCharge.getAmountOutstanding(currency);
+ }
+
+ LocalDate startDate = loanTransaction.getLoan().getDisbursementDate();
+
+ Money unprocessed = loanTransaction.getAmount(currency);
+ for (final LoanRepaymentScheduleInstallment installment :
installments) {
+ boolean isDue = installment.isFirstPeriod()
+ ?
loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate,
installment.getDueDate())
+ :
loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate,
installment.getDueDate());
+ if (isDue) {
+ Integer installmentNumber = installment.getInstallmentNumber();
+ Money paidAmount =
loanCharge.updatePaidAmountBy(amountToBePaid, installmentNumber, zero);
+
+ LoanTransactionToRepaymentScheduleMapping
loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
+ transactionMappings, loanTransaction, installment,
currency);
+
+ if (loanTransaction.isPenaltyPayment()) {
+ penaltyChargesPortion =
installment.payPenaltyChargesComponent(loanTransaction.getTransactionDate(),
paidAmount);
+ loanTransaction.setLoanChargesPaid(Collections
+ .singleton(new LoanChargePaidBy(loanTransaction,
loanCharge, paidAmount.getAmount(), installmentNumber)));
+
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero,
zero, penaltyChargesPortion);
+ } else {
+ feeChargesPortion =
installment.payFeeChargesComponent(loanTransaction.getTransactionDate(),
paidAmount);
+ loanTransaction.setLoanChargesPaid(Collections
+ .singleton(new LoanChargePaidBy(loanTransaction,
loanCharge, paidAmount.getAmount(), installmentNumber)));
+
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero,
feeChargesPortion, zero);
+ }
+
+ loanTransaction.updateComponents(zero, zero,
feeChargesPortion, penaltyChargesPortion);
+ unprocessed =
loanTransaction.getAmount(currency).minus(paidAmount);
+
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
+ }
+ }
+
+ if (unprocessed.isGreaterThanZero()) {
+ processTransaction(loanTransaction, currency, installments,
unprocessed, charges);
+ }
+ }
+
+ private void processTransaction(LoanTransaction loanTransaction,
MonetaryCurrency currency,
+ List<LoanRepaymentScheduleInstallment> installments, Money
transactionAmountUnprocessed, Set<LoanCharge> charges) {
Money zero = Money.zero(currency);
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings =
new ArrayList<>();
@@ -345,116 +494,46 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
balances.getAggregatedFeeChargesPortion(),
balances.getAggregatedPenaltyChargesPortion());
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
+ payAdditionalCharges(loanTransaction, currency, charges);
+
+ handleOverpayment(transactionAmountUnprocessed, loanTransaction);
+ }
+
+ private void payAdditionalCharges(LoanTransaction loanTransaction,
MonetaryCurrency currency, Set<LoanCharge> charges) {
+ final Set<LoanCharge> paidFeeCharges =
loanTransaction.getLoanChargesPaid().stream() //
+ .map(LoanChargePaidBy::getLoanCharge) //
+ .filter(LoanCharge::isFeeCharge).collect(Collectors.toSet());
+ final Set<LoanCharge> paidPenaltyCharges =
loanTransaction.getLoanChargesPaid().stream() //
+ .map(LoanChargePaidBy::getLoanCharge) //
+
.filter(LoanCharge::isPenaltyCharge).collect(Collectors.toSet());
// TODO: rewrite to provide sorted list
final Set<LoanCharge> loanFees = extractFeeCharges(charges);
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
+ loanFees.removeAll(paidFeeCharges);
+ loanPenalties.removeAll(paidPenaltyCharges);
+
+ BigDecimal sumFeePaidAmount = paidFeeCharges.stream() //
+ .map(paidCharge -> paidCharge.getAmountPaid(currency)) //
+ .map(Money::getAmount) //
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+ BigDecimal sumPenaltyPaidAmount = paidPenaltyCharges.stream() //
+ .map(paidCharge -> paidCharge.getAmountPaid(currency)) //
+ .map(Money::getAmount) //
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
if (loanTransaction.isNotWaiver() && !loanTransaction.isAccrual()) {
- Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
- Money penaltyCharges =
loanTransaction.getPenaltyChargesPortion(currency);
+ Money feeCharges =
loanTransaction.getFeeChargesPortion(currency).minus(sumFeePaidAmount);
+ Money penaltyCharges =
loanTransaction.getPenaltyChargesPortion(currency).minus(sumPenaltyPaidAmount);
+
if (feeCharges.isGreaterThanZero()) {
- // TODO: Rewrite to exclude charge payment
updateChargesPaidAmountBy(loanTransaction, feeCharges,
loanFees, null);
}
if (penaltyCharges.isGreaterThanZero()) {
- // TODO: Rewrite to exclude charge payment
updateChargesPaidAmountBy(loanTransaction, penaltyCharges,
loanPenalties, null);
}
}
- handleOverpayment(transactionAmountUnprocessed, loanTransaction);
- }
-
- private LoanTransactionToRepaymentScheduleMapping getTransactionMapping(
- List<LoanTransactionToRepaymentScheduleMapping>
transactionMappings, LoanTransaction loanTransaction,
- LoanRepaymentScheduleInstallment currentInstallment,
MonetaryCurrency currency) {
- Money zero = Money.zero(currency);
- LoanTransactionToRepaymentScheduleMapping
loanTransactionToRepaymentScheduleMapping = transactionMappings.stream()
- .filter(e -> loanTransaction.equals(e.getLoanTransaction()))
- .filter(e ->
currentInstallment.equals(e.getLoanRepaymentScheduleInstallment())).findFirst().orElse(null);
- if (loanTransactionToRepaymentScheduleMapping == null) {
- loanTransactionToRepaymentScheduleMapping =
LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
- currentInstallment, zero, zero, zero, zero);
- transactionMappings.add(loanTransactionToRepaymentScheduleMapping);
- }
- return loanTransactionToRepaymentScheduleMapping;
- }
-
- private Money payAllocation(PaymentAllocationType paymentAllocationType,
LoanRepaymentScheduleInstallment currentInstallment,
- LoanTransaction loanTransaction, Money
transactionAmountUnprocessed,
- LoanTransactionToRepaymentScheduleMapping
loanTransactionToRepaymentScheduleMapping, Balances balances) {
- LocalDate transactionDate = loanTransaction.getTransactionDate();
- Money zero = transactionAmountUnprocessed.zero();
- return switch (paymentAllocationType.getAllocationType()) {
- case PENALTY -> {
- Money subPenaltyPortion =
currentInstallment.payPenaltyChargesComponent(transactionDate,
transactionAmountUnprocessed);
-
balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().add(subPenaltyPortion));
-
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero,
zero, subPenaltyPortion);
- yield subPenaltyPortion;
- }
- case FEE -> {
- Money subFeePortion =
currentInstallment.payFeeChargesComponent(transactionDate,
transactionAmountUnprocessed);
-
balances.setAggregatedFeeChargesPortion(balances.getAggregatedFeeChargesPortion().add(subFeePortion));
-
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero,
subFeePortion, zero);
- yield subFeePortion;
- }
- case INTEREST -> {
- Money subInterestPortion =
currentInstallment.payInterestComponent(transactionDate,
transactionAmountUnprocessed);
-
balances.setAggregatedInterestPortion(balances.getAggregatedInterestPortion().add(subInterestPortion));
-
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero,
subInterestPortion, zero, zero);
- yield subInterestPortion;
- }
- case PRINCIPAL -> {
- Money subPrincipalPortion =
currentInstallment.payPrincipalComponent(transactionDate,
transactionAmountUnprocessed);
-
balances.setAggregatedPrincipalPortion(balances.getAggregatedPrincipalPortion().add(subPrincipalPortion));
-
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping,
subPrincipalPortion, zero, zero, zero);
- yield subPrincipalPortion;
- }
- };
- }
-
- private void
addToTransactionMapping(LoanTransactionToRepaymentScheduleMapping
loanTransactionToRepaymentScheduleMapping,
- Money principalPortion, Money interestPortion, Money feePortion,
Money penaltyPortion) {
- BigDecimal aggregatedPenalty = ObjectUtils
-
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPenaltyChargesPortion(),
BigDecimal.ZERO)
- .add(penaltyPortion.getAmount());
- BigDecimal aggregatedFee = ObjectUtils
-
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getFeeChargesPortion(),
BigDecimal.ZERO)
- .add(feePortion.getAmount());
- BigDecimal aggregatedInterest = ObjectUtils
-
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getInterestPortion(),
BigDecimal.ZERO)
- .add(interestPortion.getAmount());
- BigDecimal aggregatedPrincipal = ObjectUtils
-
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPrincipalPortion(),
BigDecimal.ZERO)
- .add(principalPortion.getAmount());
-
loanTransactionToRepaymentScheduleMapping.setComponents(aggregatedPrincipal,
aggregatedInterest, aggregatedFee, aggregatedPenalty);
- }
-
- private void handleOverpayment(Money overpaymentPortion, LoanTransaction
loanTransaction) {
- if (overpaymentPortion.isGreaterThanZero()) {
- onLoanOverpayment(loanTransaction, overpaymentPortion);
- loanTransaction.updateOverPayments(overpaymentPortion);
- }
- }
-
- private void handleChargeOff(LoanTransaction loanTransaction,
MonetaryCurrency currency,
- List<LoanRepaymentScheduleInstallment> installments) {
- loanTransaction.resetDerivedComponents();
- // determine how much is outstanding total and breakdown for
principal, interest and charges
- Money principalPortion = Money.zero(currency);
- Money interestPortion = Money.zero(currency);
- Money feeChargesPortion = Money.zero(currency);
- Money penaltychargesPortion = Money.zero(currency);
- for (final LoanRepaymentScheduleInstallment currentInstallment :
installments) {
- if (currentInstallment.isNotFullyPaidOff()) {
- principalPortion =
principalPortion.plus(currentInstallment.getPrincipalOutstanding(currency));
- interestPortion =
interestPortion.plus(currentInstallment.getInterestOutstanding(currency));
- feeChargesPortion =
feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(currency));
- penaltychargesPortion =
penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency));
- }
- }
-
- loanTransaction.updateComponentsAndTotal(principalPortion,
interestPortion, feeChargesPortion, penaltychargesPortion);
}
@AllArgsConstructor
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargePaymentWithAdvancedPaymentAllocationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargePaymentWithAdvancedPaymentAllocationTest.java
new file mode 100644
index 000000000..33601c650
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargePaymentWithAdvancedPaymentAllocationTest.java
@@ -0,0 +1,360 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import static
org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity.LIABILITY_TRANSFER;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.AdvancedPaymentData;
+import org.apache.fineract.client.models.BusinessDateRequest;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PaymentAllocationOrder;
+import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdRequest;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.integrationtests.common.BusinessDateHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import
org.apache.fineract.integrationtests.common.accounting.FinancialActivityAccountHelper;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import
org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import
org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import
org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(LoanTestLifecycleExtension.class)
+@Slf4j
+public class LoanChargePaymentWithAdvancedPaymentAllocationTest {
+
+ private static final String DATETIME_PATTERN = "dd MMMM yyyy";
+ private static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+ private static final DateTimeFormatter DATE_FORMATTER = new
DateTimeFormatterBuilder().appendPattern(DATETIME_PATTERN).toFormatter();
+ private static RequestSpecification requestSpec;
+ private static ResponseSpecification responseSpec;
+ private static LoanTransactionHelper loanTransactionHelper;
+ private static AccountHelper accountHelper;
+ private static Integer commonLoanProductId;
+ private static PostClientsResponse client;
+ private static BusinessDateHelper businessDateHelper;
+ private static SchedulerJobHelper scheduleJobHelper;
+ private static SavingsAccountHelper savingsAccountHelper;
+ private static SavingsProductHelper savingsProductHelper;
+ private static FinancialActivityAccountHelper
financialActivityAccountHelper;
+ private static Integer financialActivityAccountId;
+ private static Account liabilityTransferAccount;
+
+ @BeforeAll
+ public static void setup() {
+ Utils.initializeRESTAssured();
+ ClientHelper clientHelper = new ClientHelper(requestSpec,
responseSpec);
+ requestSpec = new
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+ requestSpec.header("Authorization", "Basic " +
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ requestSpec.header("Fineract-Platform-TenantId", "default");
+ responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+ loanTransactionHelper = new LoanTransactionHelper(requestSpec,
responseSpec);
+ accountHelper = new AccountHelper(requestSpec, responseSpec);
+ final Account assetAccount = accountHelper.createAssetAccount();
+ final Account incomeAccount = accountHelper.createIncomeAccount();
+ final Account expenseAccount = accountHelper.createExpenseAccount();
+ final Account overpaymentAccount =
accountHelper.createLiabilityAccount();
+ client =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+ businessDateHelper = new BusinessDateHelper();
+ scheduleJobHelper = new SchedulerJobHelper(requestSpec);
+ savingsAccountHelper = new SavingsAccountHelper(requestSpec,
responseSpec);
+ savingsProductHelper = new SavingsProductHelper();
+ commonLoanProductId = createLoanProduct("500", "15", "4",
assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
+ financialActivityAccountHelper = new
FinancialActivityAccountHelper(requestSpec);
+
+ List<HashMap> financialActivities =
financialActivityAccountHelper.getAllFinancialActivityAccounts(responseSpec);
+ if (financialActivities.isEmpty()) {
+ /** Setup liability transfer account **/
+ /** Create a Liability and an Asset Transfer Account **/
+ liabilityTransferAccount = accountHelper.createLiabilityAccount();
+ Assertions.assertNotNull(liabilityTransferAccount);
+
+ /*** Create A Financial Activity to Account Mapping **/
+ financialActivityAccountId = (Integer)
financialActivityAccountHelper.createFinancialActivityAccount(
+ LIABILITY_TRANSFER.getValue(),
liabilityTransferAccount.getAccountID(), responseSpec,
+ CommonConstants.RESPONSE_RESOURCE_ID);
+ Assertions.assertNotNull(financialActivityAccountId);
+ } else {
+ for (HashMap financialActivity : financialActivities) {
+ HashMap financialActivityData = (HashMap)
financialActivity.get("financialActivityData");
+ if
(financialActivityData.get("id").equals(FinancialActivityAccountsTest.LIABILITY_TRANSFER_FINANCIAL_ACTIVITY_ID))
{
+ HashMap glAccountData = (HashMap)
financialActivity.get("glAccountData");
+ liabilityTransferAccount = new Account((Integer)
glAccountData.get("id"), Account.AccountType.LIABILITY);
+ financialActivityAccountId = (Integer)
financialActivity.get("id");
+ break;
+ }
+ }
+ }
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ Integer deletedFinancialActivityAccountId =
financialActivityAccountHelper
+ .deleteFinancialActivityAccount(financialActivityAccountId,
responseSpec, CommonConstants.RESPONSE_RESOURCE_ID);
+ Assertions.assertNotNull(deletedFinancialActivityAccountId);
+ Assertions.assertEquals(financialActivityAccountId,
deletedFinancialActivityAccountId);
+ }
+
+ @Test
+ public void feeAndPenaltyChargePaymentWithDefaultAllocationRuleTest() {
+ try {
+
+ final String jobName = "Transfer Fee For Loans From Savings";
+ final String startDate = "10 April 2022";
+
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.TRUE);
+ businessDateHelper.updateBusinessDate(new
BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+ .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en"));
+
+ final Integer savingsId =
createSavingsAccountDailyPosting(client.getClientId().intValue(), startDate);
+
+ savingsAccountHelper.depositToSavingsAccount(savingsId, "10000",
startDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+ final PostLoansResponse loanResponse =
applyForLoanApplication(client.getClientId(), commonLoanProductId, 1000L, 45,
15, 3, 0,
+ "01 January 2023", "01 January 2023");
+
+ int loanId = loanResponse.getLoanId().intValue();
+
+ loanTransactionHelper.updateLoan(loanId,
+ updateLoanJson(client.getClientId().intValue(),
commonLoanProductId, savingsId.toString()));
+
+ loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
+ new
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
+ .approvedOnDate("01 January 2023").locale("en"));
+
+ loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+ new PostLoansLoanIdRequest().actualDisbursementDate("01
January 2023").dateFormat(DATETIME_PATTERN)
+
.transactionAmount(BigDecimal.valueOf(1000.00)).locale("en"));
+
+ final float feePortion = 50.0f;
+ final float penaltyPortion = 100.0f;
+
+ Integer fee = ChargesHelper.createCharges(requestSpec,
responseSpec,
+
ChargesHelper.getLoanSpecifiedDueDateWithAccountTransferJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
+ String.valueOf(feePortion), false));
+
+ Integer penalty = ChargesHelper.createCharges(requestSpec,
responseSpec,
+
ChargesHelper.getLoanSpecifiedDueDateWithAccountTransferJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
+ String.valueOf(penaltyPortion), true));
+
+ LocalDate targetDate = LocalDate.of(2023, 1, 3);
+ final String penaltyChargeAddedDate =
DATE_FORMATTER.format(targetDate);
+ loanTransactionHelper.addChargesForLoan(loanId,
LoanTransactionHelper
+
.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee),
penaltyChargeAddedDate, String.valueOf(feePortion)));
+
+ loanTransactionHelper.addChargesForLoan(loanId,
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
+ String.valueOf(penalty), penaltyChargeAddedDate,
String.valueOf(penaltyPortion)));
+
+ loanTransactionHelper.noAccrualTransactionForRepayment(loanId);
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails((long) loanId);
+
+ assertEquals(5,
loanDetails.getRepaymentSchedule().getPeriods().size());
+ assertEquals(feePortion,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesDue());
+ assertEquals(feePortion,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesOutstanding());
+ assertEquals(penaltyPortion,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesDue());
+ assertEquals(penaltyPortion,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding());
+ assertEquals(400.0f,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod());
+ assertEquals(400.0f,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod());
+ assertEquals(LocalDate.of(2023, 1, 16),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
+
+ scheduleJobHelper.executeAndAwaitJob(jobName);
+
+ loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
+ assertEquals(5,
loanDetails.getRepaymentSchedule().getPeriods().size());
+ assertEquals(feePortion,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesDue());
+ assertEquals(0,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesOutstanding());
+ assertEquals(penaltyPortion,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesDue());
+ assertEquals(0,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding());
+ assertEquals(400,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod());
+ assertEquals(250,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod());
+ assertEquals(LocalDate.of(2023, 1, 16),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
+ } finally {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.FALSE);
+ }
+ }
+
+ private Integer createSavingsAccountDailyPosting(final Integer clientID,
String startDate) {
+ final Integer savingsProductID = createSavingsProductDailyPosting();
+ Assertions.assertNotNull(savingsProductID);
+ final Integer savingsId =
savingsAccountHelper.applyForSavingsApplicationOnDate(clientID,
savingsProductID, ACCOUNT_TYPE_INDIVIDUAL,
+ startDate);
+ Assertions.assertNotNull(savingsId);
+ HashMap savingsStatusHashMap =
savingsAccountHelper.approveSavingsOnDate(savingsId, startDate);
+ SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+ savingsStatusHashMap =
savingsAccountHelper.activateSavingsAccount(savingsId, startDate);
+ SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+ return savingsId;
+ }
+
+ private Integer createSavingsProductDailyPosting() {
+ final String savingsProductJSON =
savingsProductHelper.withInterestCompoundingPeriodTypeAsDaily()
+
.withInterestPostingPeriodTypeAsMonthly().withInterestCalculationPeriodTypeAsDailyBalance()
+ .withMinimumOpenningBalance("10000.0").build();
+ return SavingsProductHelper.createSavingsProduct(savingsProductJSON,
requestSpec, responseSpec);
+ }
+
+ private static Integer createLoanProduct(final String principal, final
String repaymentAfterEvery, final String numberOfRepayments,
+ final Account... accounts) {
+ AdvancedPaymentData defaultAllocation =
createDefaultPaymentAllocation();
+ AdvancedPaymentData goodwillCreditAllocation =
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT");
+ AdvancedPaymentData merchantIssuedRefundAllocation =
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION");
+ AdvancedPaymentData payoutRefundAllocation =
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT");
+ log.info("------------------------------CREATING NEW LOAN PRODUCT
---------------------------------------");
+ final String loanProductJSON = new
LoanProductTestBuilder().withMinPrincipal(principal).withPrincipal(principal)
+
.withRepaymentTypeAsDays().withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments)
+ .withEnableDownPayment(true, "25",
true).withinterestRatePerPeriod("0").withInterestRateFrequencyTypeAsMonths()
+
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts)
+ .addAdvancedPaymentAllocation(defaultAllocation,
goodwillCreditAllocation, merchantIssuedRefundAllocation,
+ payoutRefundAllocation)
+
.withDaysInMonth("30").withDaysInYear("365").withMoratorium("0",
"0").build(null);
+ return loanTransactionHelper.getLoanProductId(loanProductJSON);
+ }
+
+ private static PostLoansResponse applyForLoanApplication(final Long
clientId, final Integer loanProductId, final Long principal,
+ final int loanTermFrequency, final int repaymentAfterEvery, final
int numberOfRepayments, final int interestRate,
+ final String expectedDisbursementDate, final String
submittedOnDate) {
+ log.info("--------------------------------APPLYING FOR LOAN
APPLICATION--------------------------------");
+ return loanTransactionHelper.applyLoan(new
PostLoansRequest().clientId(clientId).productId(loanProductId.longValue())
+
.expectedDisbursementDate(expectedDisbursementDate).dateFormat(DATETIME_PATTERN)
+
.transactionProcessingStrategyCode(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+
.locale("en").submittedOnDate(submittedOnDate).amortizationType(1).interestRatePerPeriod(interestRate)
+
.interestCalculationPeriodType(1).interestType(0).repaymentFrequencyType(0).repaymentEvery(repaymentAfterEvery)
+
.repaymentFrequencyType(0).numberOfRepayments(numberOfRepayments).loanTermFrequency(loanTermFrequency)
+
.loanTermFrequencyType(0).principal(BigDecimal.valueOf(principal)).loanType("individual"));
+ }
+
+ private String updateLoanJson(final Integer clientID, final Integer
loanProductID, String savingsId) {
+ log.info("--------------------------------APPLYING FOR LOAN
APPLICATION--------------------------------");
+ List<HashMap> collaterals = new ArrayList<>();
+ final Integer collateralId =
CollateralManagementHelper.createCollateralProduct(this.requestSpec,
this.responseSpec);
+ Assertions.assertNotNull(collateralId);
+ final Integer clientCollateralId =
CollateralManagementHelper.createClientCollateral(this.requestSpec,
this.responseSpec,
+ clientID.toString(), collateralId);
+ Assertions.assertNotNull(clientCollateralId);
+ addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
+
+ final String loanApplicationJSON = new LoanApplicationTestBuilder() //
+ .withPrincipal("1,000.00") //
+ .withLoanTermFrequency("45") //
+ .withLoanTermFrequencyAsDays() //
+ .withNumberOfRepayments("3") //
+ .withRepaymentEveryAfter("15") //
+ .withRepaymentFrequencyTypeAsDays() //
+ .withInterestRatePerPeriod("0") //
+
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+ .withAmortizationTypeAsEqualInstallments() //
+ .withInterestTypeAsDecliningBalance() //
+ .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
+ .withExpectedDisbursementDate("01 January 2023") //
+ .withSubmittedOnDate("01 January 2023") //
+ .withCollaterals(collaterals) //
+ .build(clientID.toString(), loanProductID.toString(),
savingsId);
+ return loanApplicationJSON;
+ }
+
+ private void addCollaterals(List<HashMap> collaterals, Integer
collateralId, BigDecimal quantity) {
+ collaterals.add(collaterals(collateralId, quantity));
+ }
+
+ private HashMap<String, String> collaterals(Integer collateralId,
BigDecimal quantity) {
+ HashMap<String, String> collateral = new HashMap<>(2);
+ collateral.put("clientCollateralId", collateralId.toString());
+ collateral.put("quantity", quantity.toString());
+ return collateral;
+ }
+
+ private static AdvancedPaymentData createPaymentAllocation(String
transactionType, String futureInstallmentAllocationRule) {
+ AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
+ advancedPaymentData.setTransactionType(transactionType);
+
advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule);
+
+ List<PaymentAllocationOrder> paymentAllocationOrders =
getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
+ PaymentAllocationType.PAST_DUE_FEE,
PaymentAllocationType.PAST_DUE_PRINCIPAL,
PaymentAllocationType.PAST_DUE_INTEREST,
+ PaymentAllocationType.DUE_PENALTY,
PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
+ PaymentAllocationType.DUE_INTEREST,
PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
+ PaymentAllocationType.IN_ADVANCE_PRINCIPAL,
PaymentAllocationType.IN_ADVANCE_INTEREST);
+
+ advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
+ return advancedPaymentData;
+ }
+
+ private static AdvancedPaymentData createDefaultPaymentAllocation() {
+ AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
+ advancedPaymentData.setTransactionType("DEFAULT");
+
advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
+
+ List<PaymentAllocationOrder> paymentAllocationOrders =
getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
+ PaymentAllocationType.PAST_DUE_FEE,
PaymentAllocationType.PAST_DUE_PRINCIPAL,
PaymentAllocationType.PAST_DUE_INTEREST,
+ PaymentAllocationType.DUE_PENALTY,
PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
+ PaymentAllocationType.DUE_INTEREST,
PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
+ PaymentAllocationType.IN_ADVANCE_PRINCIPAL,
PaymentAllocationType.IN_ADVANCE_INTEREST);
+
+ advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
+ return advancedPaymentData;
+ }
+
+ private static List<PaymentAllocationOrder>
getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
+ AtomicInteger integer = new AtomicInteger(1);
+ return Arrays.stream(paymentAllocationTypes).map(pat -> {
+ PaymentAllocationOrder paymentAllocationOrder = new
PaymentAllocationOrder();
+ paymentAllocationOrder.setPaymentAllocationRule(pat.name());
+ paymentAllocationOrder.setOrder(integer.getAndIncrement());
+ return paymentAllocationOrder;
+ }).toList();
+ }
+}